Rumburak
Rumburak

Reputation: 3571

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?

Upvotes: 25

Views: 1045

Answers (4)

Andrzej
Andrzej

Reputation: 5127

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.

Upvotes: 4

grisumbras
grisumbras

Reputation: 860

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…

Upvotes: 2

Andrei R.
Andrei R.

Reputation: 2462

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>).

Upvotes: 1

William Jones
William Jones

Reputation: 204

I think the answer is : yes.

first the static_assert forces the constructor from "definition" to a declaration.

I am not sure exactly about the template nature of the static_assert with regard to the the 12.8 section below either...

(I apologize for the formatting...)

c © ISO/IEC N3242=11-0012 7 Declarations [dcl.dcl]

2. A declaration is a definition unless it declares a function without specifying the function’s body (8.4), it contains the extern specifier (7.1.1) or a linkage-specification 25(7.5) and neither an initializer nor a function-body, it declares a static data member in a class definition (9.4), it is a class name declaration (9.1), it is an opaque-enum-declaration(7.2), or it is a typedef declaration (7.1.3), a using-declaration(7.3.3), a static_assert-declaration(Clause 7), an attribute-declaration (Clause 7), an empty-declaration (Clause 7), or a using-directive (7.3.4)

12.8 Copying and moving class objects [class.copy]

7 A member function template is never instantiated to perform the copy of a class object to an object of its class type. [Example:

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

S f();
const S g;

void h() {
S a( f() );// does not instantiate member template;
// uses the implicitly generated move constructor


S b(g);// does not instantiate the member template;
// uses the implicitly generated copy constructor
}

— end example ]

32 When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. 123 - This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies): — in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

Upvotes: -1

Related Questions