Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 899
Rumburak Avatar asked Apr 02 '16 09:04

Rumburak


3 Answers

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.

like image 163
Andrzej Avatar answered Nov 10 '22 04:11

Andrzej


I believe the answer is no. My logic goes like this:

  1. Copy elision requires declaration of copy/move constructors but doesn't require definition.
  2. Member function definitions of templates are not instantiated unless their definitions are required.
  3. If a definition is not instantiated it cannot be tested for being ill-formed.

References:

14.7.1.1 …The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions, default arguments, or exception-specifications of the class member functions…

14.7.1.2 Unless a member of a class template… has been explicitly instantiated or explicitly specialized, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist…

like image 2
grisumbras Avatar answered Nov 10 '22 04:11

grisumbras


move constructors are not called. static_assert is evaluated upon instantiation of X<int>::X(X&&). Most probably, some compilers evaluate template methods upon use (when you use move constructor, and you don't use it), and others - upon instantiation of class template (when you first use X<int>).

like image 1
Andrei R. Avatar answered Nov 10 '22 03:11

Andrei R.