Default move constructor failing when used in a constexpr context,

Viewed 126

I have an issue with the default move constructor in Visual Studio 2022 (/std:c++latest) in a constexpr context. I do not see the issue in Visual Studio 2019. I have two questions:

  • Is it my code or Visual Studio 2022 that is incorrect? If my code is incorrect then why?
  • Are similar issues are seen in gcc or clang? (I do currently have access to those compilers.)

Problem statement
In the code below, I have two true/false switches: DEFAULT_MOVE and CONSTEXPR, therefore giving me four possible programs.

  • DEFAULT_MOVE controls whether the default move constuctor or a home-brew move constructor is used.

  • CONSTEXPR controls whether an instance of Container is created at compile-time or run-time.

The code does not compile when both DEFAULT_MOVE and CONSTEXPR are both true, yielding the message:
error C3615: constexpr function 'Containerstd::u8string_view,3::Container' cannot result in a constant expression

However, under any other of the three switch combinations the code compiles successfully.

I would have expected the code to compile in all four cases.

#include <array>
#include <cassert>
#include <string_view>

//switches
#define DEFAULT_MOVE true
#define CONSTEXPR true

template<typename T, size_t N>
struct Container{
  std::array<T, N> x; 

  template<typename... Args>
  constexpr explicit Container(Args&&...args) : x{std::forward<Args>(args)...} {}

#if DEFAULT_MOVE
  //default move ctor
  constexpr Container(Container&&) noexcept = default;
#else
  //home-brew move ctor
  constexpr Container(Container&& other) noexcept : x{} {
    for (size_t n{0} ; T& e : other.x)
      x[n++] = std::move(e);
  }
#endif

  constexpr Container(const Container&) = delete;
  constexpr Container& operator=(Container&&) = delete;
  constexpr Container& operator=(const Container&) = delete;
  constexpr ~Container() = default;
};

using X = Container<std::u8string_view, 3>;
using Y = Container<X, 2>;

int main() {
#if CONSTEXPR
  // constexpr creation of a Y object
  constexpr Y a{X{u8"a",u8"b"}, X{u8"a"}};
  static_assert(a.x[0].x[1] == u8"b");

#else
  // runtime creation of a Y object
  const Y a{X{u8"a",u8"b"}, X{u8"a"}};
  assert(a.x[0].x[1] == u8"b");
#endif  
}
1 Answers

You should use: constexpr Container(Container&&) = default;. The default implementation is already noexcept in most cases.

From cppreference:

default constructors, copy constructors, move constructors that are implicitly-declared or defaulted on their first declaration unless:

  • a constructor for a base or member that the implicit definition of the constructor would call is potentially-throwing
  • a subexpression of such an initialization, such as a default argument expression, is potentially-throwing
  • a default member initializer (for default constructor only) is potentially-throwing

Best way to validate things like this is using https://godbolt.org/. It let's you try out a lot of different compilers quickly.

Add #include <cstddef> to make size_t work without std::size.

Clang/LLVM v13 can build this with -std=c++20, as can Visual C++ as with /std:c++20 (19.30 on godbolt is VS 2022's compiler).

I tried /std:c++latest and it also works.

What is the full set of compiler switches you are using? Also try cl -Bv and see what version of the compiler exactly you are using.

Related