-
Notifications
You must be signed in to change notification settings - Fork 8
Description
Full name of submitter: Anoop S. Rana
Reference (section label): temp.inst
Link to reflector thread (if any): https://stackoverflow.com/questions/79844065/is-gcc-right-when-it-accepts-a-class-template-having-a-member-with-a-wrong-defau
Issue description: Consider the following program which shows implementation divergence. MSVC, GCC, EDG accept the program while Clang rejects it.
struct Wrapper
{
explicit Wrapper(int) {}
};
template <int N = 0>
struct Test
{
Test() : w(0) {}
Wrapper w{}; //MSVC, GCC, EDG: Ok but Clang rejects
};
Test t;
int main() {
}
There is also an old gcc bug that says this is ill-formed. For completeness, I'll describe what I think is the intention and what is given in the current standard. As a summary, I think one intention here is to make this ill-formed because the default ctor won't be implicitly generated for Wrapper so even writing Wrapper w{}; shouldn't be possible but since templates allow lazy instantiation and more importantly since the initializer is not supposed to be needed for non constexpr contexts, the other intention may be to allow it. Now, let's see what the current standard says about this:
First note that Wrapper w{}; is direct initialisation(direct list initialization to be more specific). From dcl.init:
The initialization that occurs:
- for an initializer that is a parenthesized expression-list or a braced-init-list,
is called direct-initialization.
Next we move to the semantics of the initializer.
- The semantics of initializers are as follows. The destination type is the cv-unqualified type of the object or reference being initialized and the source type is the type of the initializer expression. If the initializer is not a single (possibly parenthesized) expression, the source type is not defined.
- If the initializer is a (non-parenthesized) braced-init-list or is = braced-init-list, the object or reference is list-initialized ([dcl.init.list])..
The above means that, the object is to be list-initialized.
From list initialization:
List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is called an initializer list, and the comma-separated initializer-clauses of the initializer-list or designated-initializer-clauses of the designated-initializer-list are called the elements of the initializer list. An initializer list may be empty. List-initialization can occur in direct-initialization or copy-initialization contexts; list-initialization in a direct-initialization context is called direct-list-initialization and list-initialization in a copy-initialization context is called copy-list-initialization. Direct-initialization that is not list-initialization is called direct-non-list-initialization.
The above means that Wrapper w{}; is direct-list initiaization. Next we see the effect of list initialization:
List-initialization of an object or reference of type cv T is defined as follows:
- Otherwise, if the initializer list has no elements and T is a class type with a default constructor, the object is value-initialized.
The above means that the object will be value initialized, so we move on to value-initialization:
- To value-initialize an object of type T means:
- If T is a (possibly cv-qualified) class type ([class]), then let C be the constructor selected to default-initialize the object, if any. If C is not user-provided, the object is first zero-initialized. In all cases, the object is then default-initialized.;
This means that the object will be default initialized:
To default-initialize an object of type T means:
- If T is a (possibly cv-qualified) class type ([class]), constructors are considered. The applicable constructors are enumerated ([over.match.ctor]), and the best one for the initializer () is chosen through overload resolution ([over.match]). The constructor thus selected is called, with an empty argument list, to initialize the object.
So we move on to over.match.ctor to get a list of candidate ctors. Also do note the initializer() part in the above quoted reference as it will be used at the end as an argument.
When objects of class type are direct-initialized, copy-initialized from an expression of the same or a derived class type ([dcl.init]), or default-initialized, overload resolution selects the constructor. For direct-initialization or default-initialization (including default-initialization in the context of copy-list-initialization), the candidate functions are all the constructors of the class of the object being initialized. Otherwise, the candidate functions are all the non-explicit constructors ([class.conv.ctor]) of that class. The argument list is the expression-list or assignment-expression of the initializer. For default-initialization in the context of copy-list-initialization, if an explicit constructor is chosen, the initialization is ill-formed.
But note that there is no default constructor for Wrapper. As you've provided your own parameterized ctor, the compiler won't synthesize a default ctor for Wrapper. Thus, the condition for Wrapper w{}; to be valid(which requires a default ctor) is not fulfilled and clang seems to be correct in rejecting the program.
But note that we also have temp.inst#3:
The implicit instantiation of a class template specialization causes:
- the implicit instantiation of the declarations, but not of the definitions, of the non-deleted class member functions, member classes, scoped member enumerations, static data members, member templates, and friends; and
- the implicit instantiation of the definitions of deleted member functions, unscoped member enumerations, and member anonymous unions.
The implicit instantiation of a class template specialization does not cause the implicit instantiation of default arguments or noexcept-specifiers of the class member functions.
Note that this does not say anything about default member initializer of non-static data members, so it isn't clear(I think) if Test t; results in use of the initializer.
Suggested Resolution: I think this can be solved by specifying that only declaration of the non-static data member is instantiated(just like declaration of non-static member function) and so initializer is not used here and the program is intended to be well-formed. Note that this would also make this program also well-formed.