Search code examples
c++c++11language-lawyerstatic-assertcopy-elision

Clang and GCC vs MSVC and ICC: Is a static_assert in the copy/move constructor required to work, if copy/move elision could apply too?


I have a static_assert in a move constructor of a template struct of mine. Is this static_assert required to be considered by the compiler, even if copy elision is possible?

This is the stripped-down scenario:

#include <type_traits>

template<typename T>
struct X
{
  X(X&&) { static_assert(std::is_same<void, T>::value, "Intentional Failure"); }
};

auto impl() -> X<int>;    
auto test() -> decltype(impl())
{
  return impl();
}

int main()
{
  test();
}

GCC and Clang agree to evaluate the static_assert and fail to compile.
MSCV and ICC on the other hand compile the code just fine.

Interestingly, when I remove the definition of the move constructor and just declare it like this:

template<typename T>
struct X
{
  X(X&&);
};

GCC and Clang also compile the code now. Thus, all compilers seem to agree that the definition of the move constructor is irrelevant for copy elision.

Question:
If there is a static_assert in the copy/move constructor, does the standard require it to be evaluated even if copy/move elision is possible?


Solution

  • The following should help.

    You do not have to employ type deduction to illustrate the problem. Even the simpler example has the same issue:

    #include <type_traits>
    
    template <typename T>
    struct X
    {
      X() {}
      X(X&&) { static_assert(std::is_same<void, T>::value, "failed"); }
    };
    
    int main()
    {
      X<int> x = X<int>();
    }
    

    Clang and GCC will not compile it. MSVC compiles and executes fine.

    This shows that the problem is related to odr-use and when definitions of member functions are instantiated.

    14.7.1 [temp.inst] paragraph 2 says "[...] the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist"

    3.2 [basic.def.odr] paragraph 3 says (in a note) "[...] A constructor selected to copy or move an object of class type is odr-used even if the call is actually elided by the implementation"

    3.2 [basic.def.odr] paragraph 4 says "Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program; no diagnostic required."

    Ergo: the specialization should be instantiated, and the assertion have fired.