Reputation: 21077
What std::is_constructible
shall return for an aggregate type that does not allow creation of objects due to invalid initializer of a member field?
Consider for example
#include <type_traits>
template <class T>
struct A {
T x{};
};
static_assert( !std::is_constructible_v<A<int&>> );
A<int&> obj;
is not well-formed since it cannot initialize int&
from {}
. So I would expect that the example program above compiles fine, as it does in GCC. But MSVC accepts the opposite statement static_assert( std::is_constructible_v<A<int&>> );
presumable since the default constructor of A
was not formally deleted. And Clang behaves in the third way stopping the compilation with the error:
error: non-const lvalue reference to type 'int' cannot bind to an initializer list temporary
T x{};
^~
: note: in instantiation of default member initializer 'A<int &>::x' requested here
struct A {
^
Online demo: https://gcc.godbolt.org/z/nnxcGn7WG
Which one of the behaviors is correct according to the standard?
Upvotes: 1
Views: 237
Reputation: 76628
std::is_constructible_v<A<int&>>
checks whether a variable initialization with ()
initializer is possible. That's never aggregate initialization (not even in C++20), but always value initialization, which will use the default constructor.
Because your class doesn't declare any constructor, it still has an implicit default constructor declared. That one will be called during value initialization.
The implicit default constructor is also not defined as deleted, because none of the points in [class.default.ctor]/2 apply. There is one point for reference members without any default member initializer though.
So this constructor will be chosen in overload resolution and therefore std::is_constructible_v<A<int&>>
is true
. That the instantiation of the default constructor might be ill-formed is irrelevant. Only declarations are checked (the immediate context). So GCC's behavior is not correct.
The only remaining question now is whether the implicit default constructor (or rather the default member initializer?) is supposed to be instantiated from the std::is_constructible
test itself, causing the program to be ill-formed.
As far as I can tell the standard doesn't specify that clearly. The standard says that the noexcept-specifier of a function is implicitly instantiated when it is needed. (see [temp.inst]/15)
It also says that it is needed if it is used in an unevaluated operand in a way that would be an ODR use if it was potentially-evaluated. (see [except.spec]/13.2
Arguably the type trait has to make such a use.
Then [except.spec]/7.3 specifies that it needs to be checked whether the default member initializers are potentially-throwing in order to check whether the implicit default constructor has a potentially-throwing exception specification.
Clang seems to then follow the idea that this requires instantiation of the default member initializer and therefore causes the compilation error because that instantiation is invalid.
The problems I see with that is:
Upvotes: 3
Reputation: 70663
The standard states:
Only the validity of the immediate context of the variable initialization is considered.
and
The evaluation of the initialization can result in side effects such as the instantiation of class template specializations and function template specializations, the generation of implicitly-defined functions, and so on. Such side effects are not in the “immediate context” and can result in the program being ill-formed.
It is the generation of an the definition for an implicity-defined function (the constructor) that fails in your example. So this is not the "immediate context". That would allow:
The msvc interpretation: It doesn't consider the validity of the non-immediate context
The clang interpretation: The side effects result in your program being ill-formed
If anything it seems to me that gcc's result might be incorrect. But I'm not sure if the standard wants to forbid the consideration of the "non-immediate context" or if it simply doesn't require it.
Upvotes: 1