std::is_constructible for aggregates with invalid default initializer - c++

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?

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:
I don't see anything in the [temp.inst] about when default member initializers are instantiated or what that would mean exactly.
[temp.inst]/15 speaks of the noexcept-specifier grammar construct and since the default constructor is implicit, that doesn't really work out.

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.

Related

Compiler variance for ambiguous copy-assignment from empty-braces

I've been trying to understand the rationale for std::nullopt_t to not be allowed to be DefaultConstructible in C++17 (where it was introduced) and beyond, and stepped over some compiler variance confusion in the process.
Consider the following spec-violating (it is DefaultConstructible) implementation of nullopt_t:
struct nullopt_t {
explicit constexpr nullopt_t() = default;
};
which is an aggregate in C++11 and C++14 (no user-provided ctors), but which is not an aggregate in C++17 (explicit ctor) and C++20 (user-declared ctor).
Now consider the following example:
struct S {
constexpr S() {}
S(S const&) {}
S& operator=(S const&) { return *this; } // #1
S& operator=(nullopt_t) { return *this; } // #2
};
int main() {
S s{};
s = {}; // GCC error: ambiguous overload for 'operator=' (#1 and #2)
}
This is rejected by GCC (various versions, say v11.0) throughout C++11 to C++20, but is accepted by both Clang (say v12.0) and MSVC (v19.28) throughout C++11 to C++20.
DEMO
My initial assumptions were that the program:
is ill-formed in C++11 and C++14, as nullopt_t (as above) is an aggregate, whereas it
is well-formed in C++17 and C++20, as it is no longer an aggregate, meaning that its explicit default constructor should prohibit copy-list-init of a temporary nullopt_t object as needed for the copy assignment operator at #2 to be viable,
but none of the compilers agree in full with this theory, some I'm probably missing something.
What compiler is correct here (if any), and how do we explain it by relevant standard sections (and DR:s, if relevant)?
Why is nullopt_t required to be DefaultConstructible in the first place?
The spec requirement that nullopt_t shall not be DefaultConstructible is arguably, in retrospect, a mistake based on some LWG and CWG confusion around tag types, and the resolution of this confusion which came only after std::optional was brought in from the Library Fundamentals TS Components.
First of all, the current (C++17, C++20) spec of nullopt_t, [optional.nullopt]/2, requires [emphasis mine]:
Type nullopt_­t shall not have a default constructor or an initializer-list constructor, and shall not be an aggregate.
and its main use is described in the previous section, [optional.nullopt]/1:
[...] In particular, optional<T> has a constructor with nullopt_­t as a single argument; this indicates that an optional object not containing a value shall be constructed.
Now, P0032R3 (Homogeneous interface for variant, any and optional), one of the papers which was part of introducing std::optional, has a discussion around nullopt_t, tag types in general, and the DefaultConstructible requirement [emphasis mine]:
No default constructible
While adapting optional<T> to the new in_place_t type we found
that we cannot anymore use in_place_t{}. The authors don't consider
this a big limitation as the user can use in_place instead. It needs
to be noted that this is in line with the behavior of nullopt_t as
nullopt_t{} fails as no default constructible. However nullptr_t{}
seems to be well formed.
Not assignable from {}
After a deeper analysis we found also that the old in_place_t
supported in_place_t t = {};. The authors don't consider this a big limitation as we don't expect that a lot of users could use this and the user can use
in_place instead.
in_place_t t;
t = in_place;
It needs to be noted that this is in line with the behavior of
nullopt_t as the following compile fails.
nullopt_t t = {}; // compile fails
However nullptr_t seems to be support it.
nullptr_t t = {}; // compile pass
To re-enforce this design, there is an pending issue 2510-Tag types should not be DefaultConstructible Core issue 2510.
And indeed, the initial proposed resolution of LWG Core Issue 2510 was to require all tag types to not be DefaultConstructible [emphasis mine]:
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Previous resolution [SUPERSEDED]:
[...] Add a new paragraph after 20.2 [utility]/2 (following the header synopsis):
-?- Type piecewise_construct_t shall not have a default constructor. It shall be a literal type. Constant piecewise_construct shall be initialized with an argument of literal type.
This resolution was superseded, however, as there were overlap with CWG Core Issue 1518, which was eventually resolved in a way that did not require tag types to not be DefaultConstructible, as explicit would suffice [emphasis mine]:
(CWG) 1518. Explicit default constructors and copy-list-initialization
[...]
Additional note, October, 2015:
It has been suggested that the resolution of issue 1630 went too far in allowing use of explicit constructors for default initialization, and that default initialization should be considered to model copy initialization instead. The resolution of this issue would provide an opportunity to adjust that.
Proposed resolution (October, 2015):
Change 12.2.2.4 [over.match.ctor] paragraph 1 as follows:
[...] For direct-initialization or default-initialization, the candidate functions are all the constructors of the class of the object being initialized. [...]
as long as explicit also implied that the type was not an aggregate, which in turn was the final resolution of LWG Core Issue 2510 (based on the final resolution of CWG Core Issue 1518)
(LWG) 2510. Tag types should not be DefaultConstructible
[...]
Proposed resolution:
[...] In 20.2 [utility]/2, change the header synopsis:
// 20.3.5, pair piecewise construction
struct piecewise_construct_t { explicit piecewise_construct_t() = default; };
constexpr piecewise_construct_t piecewise_construct{};
[...]
These latter changes, however, were not brought into the proposal for std::optional, arguably an oversight, and I would like to claim that nullopt_t need not be required to not be DefaultConstructible, only, like other tag types, that it should have a user-declared explicit constructor, which bans it from a candidate for empty-braces copy-list-init both by it not being an aggregate and by the only candidate constructor being explicit.
Which compiler is right and wrong here?
Given the LWG 2510, CWG 1518 (and other) confusion, let's focus on C++17 and beyond. In this case, GCC is arguably wrong to reject the program, whereas Clang and MSVC are correct to accept it.
Why?
Because the S& operator=(nullopt_t) assignment operator is not viable for the assignment s = {};, as the empty braces {} would require either aggregate initialization or copy-list-initialization to create a nullopt_t (temporary) object. nullopt_t, however (by the idiomatic tag implementation: my implementation above), as per as per P0398R0 (which resolves CWG Core Issue 1518), is neither an aggregate nor does its default constructor participate in copy-list-initialization (from empty braces).
This likely falls under the following GCC bug report:
Bug 54835 - (C++11)(DR 1518) Explicit default constructors not respected during copy-list-initialization
which was listed as SUSPENDED in 2015-06-15, before the change in the resolution of CWG Core Issue 1630 ("resolution of issue 1630 went too far"). The ticket is now re-opened based on a ping from this Q&A.

C++ implicit definition of special functions

In the current version of the C++ draft (september 2019), paragraph [class.default.ctor]/4 states:
A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) to create an object of its class type ([intro.object]), when it is needed for constant evaluation ([expr.const]), or when it is explicitly defaulted after its first declaration. [...]. Before the defaulted default constructor for a class is implicitly defined, all the non-user-provided default constructors for its base classes and its non-static data members shall have been implicitly defined. [ Note: An implicitly-declared default constructor has an exception specification ([except.spec]). An explicitly-defaulted definition might have an implicit exception specification, see [dcl.fct.def]. — end note ]
[class.dtor]/11 specifies a similar restriction for default destructors.
What does the highlighted sentence mean? Is it a restriction on the program or on the implementation (compiler)?
The first sentence of the quoted paragraph states when a defaulted default constructor is implicitly defined (e.g. when it is odr-used). If the highlighted sentence is a restriction on the program, then the following example should be ill-formed, because at (1) the defaulted default constructor of B is odr-used, therefore it is implicitly defined. However, at this point, the defaulted default constructor of A has not been implicitly defined, therefore the restriction in the highlighted sentence is not respected. This is because I believe that the defaulted default constructor of A is odr-used only after the defaulted default constructor of B is defined. Is this assumption wrong?
struct A {};
struct B : A {};
int main()
{
B b; // (1)
}
Thank you.
I agree with the OP that the drafting is sloppy, but I believe that the intent is clear:
When the implementation implicitly defines B's default constructor, if A's default constructor hasn't been implicitly defined yet in this translation unit, then the compiler should generate the implicit definition of A's default constructor first.
In response to the OP's concern that this creates an additional point where an implicit definition might be generated (besides what is stated in the first sentence of the quoted paragraph), sure, that's one possible way to interpret it. Another way to interpret it is that the compiler starts defining A's default constructor while it's in the middle of defining B's default constructor (because B's default constructor calls A's default constructor "to create an object of its class type", thus requiring A's default constructor to be implicitly defined) and that the former must be completed before the latter. In other words, the first sentence of the quoted paragraph refers to when the generation of the implicit definition begins, and the bolded sentence refers to when it is completed. I think these two interpretations are equivalent, so it's not necessary to choose between them.

Literal class type member function constraints

The specification does not seem to put any constraints on the member functions of a literal class type
I have two questions regarding this
Q1) Do I have complete liberty over what member functions I can put in?
Q2) How do I verify if a class is a literal type? (Possible method: Define a constexpr object of it and check if it compiles?)
The only constraints on literal classes I see are:
• All the data members must have literal type.
• The class must have at least one constexpr constructor.
• If a data member has an in-class initializer, the initializer for a member of built-in type must be a constant expression, or if the member has class type, the initializer must use the member’s own constexpr constructor.
• The class must use default definition for its destructor, which is the member that destroys objects of the class type
(Source: C++ Primer, 5th edition)
Q1. Yes, you can have any methods you like (excluding constructor/destructor which have constraints). Even including virtual methods, if the constructor is constexpr.
Q2. As you say, define a constexpr variable of that type. If there is no diagnostic message (and the compiler is conforming) then the type is definitely a LiteralType. Note that it is possible for the type to be literal but the code to fail compilation for some other reason.
The definition in the Standard seems slightly clearer to me than your quoted definition. For example, there are some cases where a constexpr constructor is not required (e.g. a closure or an aggregate).

constexpr member function of non constexpr constructible class

If a non-literal class type has no constexpr constructor (it is not constexpr constructible), does a non-static constexpr member function make any sense? I mean if you cannot construct the object at compile time, how would you able to use its member functions?
Anyway, the major compilers don't complain about it, which makes me think it is allowed by the standard.
Nevertheless, you are able to use such constexpr member functions in runtime without any problem. The only question now what is the effect of constexpr in this case, if any. My best guess is that the return value of the constexpr member is being evaluated at compile-time (if possible), so on a run-time call it have to do a simple copy.
Is my guess correct, or is the constexpr specifier absolutely meaningless in this case (i.e. the member function is being evaluated at runtime)?
The premise of your question seems to be that only constexpr functions can be evaluated at compile-time.
This premise is incorrect. The compiler can precompute anything it can figure out a way to do, as long as the exact side result and side-effects are produced (as-if rule).
What constexpr provides is a guarantee that certain expressions will be evaluated at compile-time by every compiler (it's not a "quality of implementation" issue), which makes it possible to use them in contexts where a compile-time value is needed, such as non-type template arguments, operands of case clauses in switch statements, etc.
The specific details around constexpr functions include that there has to be at least one set of arguments (the target instance is an implied argument) such that the constexpr evaluation rules are met. If that isn't true, your program is ill-formed and its runtime behavior is not specified at all, so don't go adding constexpr where it doesn't logically belong.
However, compilers aren't required to diagnose violations of this rule. That means that "major compilers don't complain about it" should not be in any way interpreted as assurance that the code is correct.
Standard's wording, section 7.1.5 (draft n4582)
For a constexpr function or constexpr constructor that is neither defaulted nor a template, if no argument values exist such that an invocation of the function or constructor could be an evaluated subexpression of
a core constant expression, or, for a constructor, a constant initializer for some object, the program is ill-formed; no diagnostic required.

What am I allowed to do with a static, constexpr, in-class initialized data member?

This is probably a bit of an unusual question, in that it asks for a fuller explanation of a short answer given to another question and of some aspects of the C++11 Standard related to it.
For ease of reference, I shall sum up the referenced question here. The OP defines a class:
struct Account
{
static constexpr int period = 30;
void foo(const int &) { }
void bar() { foo(period); } //no error?
};
and is wondering why he gets no error about his usage of an in-class initialized static data member (a book mentioned this to be illegal). Johannes Schaub's answer states, that:
This violates the One Definition Rule;
No diagnostics is required.
As much as I rely the source and validity of this answer, I honestly dislike it because I personally find it too cryptic, so I tried to work out a more meaningful answer myself, with only partial success. Relevant seems to be § 9.4.2/4:
"There shall be exactly one definition of a static data member that is odr-used (3.2) in a program; no diagnostic is required" [Emphases are mine]
Which gets me a bit closer to the point. And this is how § 3.2/2 defines an odr-used variable:
"A variable whose name appears as a potentially-evaluated expression is odr-used unless it is an object that satisfies the requirements for appearing in a constant expression (5.19) and the lvalue-to-rvalue conversion (4.1) is immediately applied" [Emphases are mine]
In the OP's question, variable period clearly satisfies the requirements for appearing in a constant expression, being a constexpr variable. So the reason must be certainly found in the second condition: "and the lvalue-to-rvalue conversion (4.1) is immediately applied".
This is where I have troubles interpreting the Standard. What does this second condition actually mean? What are the situations it covers? Does it mean that a static constexpr variable is not odr-used (and therefore can be in-class initialized) if it is returned from a function?
More generally: What are you allowed to do with a static constexpr variable so that you can in-class initialize it?
Does it mean that a static constexpr variable is not odr-used (and
therefore can be in-class initialized) if it is returned from a
function?
Yes.
Essentially, as long as you treat it as a value, rather than an object, then it is not odr-used. Consider that if you pasted in the value, the code would function identically- this is when it is treated as an rvalue. But there are some scenarios where it would not.
There are only a few scenarios where lvalue-to-rvalue conversion is not performed on primitives, and that's reference binding, &obj, and probably a couple others, but it's very few. Remember that, if the compiler gives you a const int& referring to period, then you must be able to take it's address, and furthermore, this address must be the same for each TU. That means, in C++'s horrendous TU system, that there must be one explicit definition.
If it is not odr-used, the compiler can make a copy in each TU, or substitute the value, or whatever it wants, and you can't observe the difference.
You missed a part of the premise. The class definition give above is completely valid, if you also define Account::period somewhere (but without providing an initializer). See the last sentance of 9.4.2/3:
The member shall still be defined in a namespace scope if it is odr-used
(3.2) in the program and the namespace scope definition shall not
contain an initializer.
This entire discussion only applies to static constexpr data members, not to namespace-scope static variables. When static data members are constexpr (rather than simply const) you have to initialize them in the class definition. So your question should really be: What are you allowed to do with a static constexpr data member so that you don't have to provide a definition for it at namespace scope?
From the preceding, the answer is that the member must not be odr-used. And you already found relevant parts of the definition of odr-used. So you can use the member in a context where it is not potentially-evaluated - as an unevaluated operand (for example of sizeofor decltype) or a subexpression thereof. And you can use it in an expression where the lvalue-to-rvalue conversion is immediately applied.
So now we are down to What uses of a variable cause an immediate lvalue to rvalue conversion?
Part of that answer is in §5/8:
Whenever a glvalue expression appears as an operand of an operator
that expects a prvalue for that operand, the lvalue-to-rvalue (4.1),
array-to-pointer (4.2), or function-to-pointer (4.3) standard
conversions are applied to convert the expression to a prvalue.
For arithmetic types that essentially applies to all operators that apply standard arithmetic conversions. So you can use the member in various arithmetic and logical operations without needing a definition.
I can't enumerate all things, you can or can't do here, because the requirements that something be a [g]lvalue or [p]rvalue are spread across the standard. The rule of thumb is: if only the value of the variable is used, a lvalue to rvalue conversion is applied; if the variable is used as an object, it is used as lvalue.
You can't use it in contexts where an lvalue is explicitly required, for example as argument to the address-of operator or mutating operators). Direct binding of lvalue references (without conversion) is such a context.
Some more examples (without detailed standardese analysis):
You can pass it to functions, unless the function parameter is a reference to which the variable can be directly bound.
For returning it from a function: if implicit conversion to the return type involves a user-defined conversion function, the rules for passing to a function apply. Otherwise you can return it, unless the function returns an lvalue (a reference) referring directly to the variable.
The key rule for odr-used variables, the "One Definition Rule" is in 3.2/3:
Every program shall contain exactly one definition of every non-inline
function or variable that is odr-used in that program; no diagnostic
required.
The "no diagnostic required" part means that programs violating this rule cause undefined behavior, which may range from failing to compile, compiling and failing in surprising ways to compiling and acting as if everything was OK. And your compiler need not warn you about the problem.
The reason, as others have already indicated, is that many of these violations would only be detected by a linker. But optimizations may have removed references to objects, so that no cause for linkage failure remains or else linking may sometimes occur only at runtime or be defined to pick an arbitrary instance from multiple definitions of a name.