Skip to content

CWG3175 [temp.inst] Clarify if initializer is needed when implicitly instantiating a non-static data member that is not used in constexpr context #830

Description

@ranaanoop

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.

  1. 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:

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

Activity

  1. ranaanoop commented on Dec 12, 2025

    @ranaanoop
    Author

    MSVC, GCC and EDG accept the program while Clang rejects it. Demo. Here is the program:

    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() {
    }
    
  2. jensmaurer commented on Dec 12, 2025

    @jensmaurer
    Member

    EDG also rejects in native mode.

  3. jensmaurer commented on Dec 12, 2025

    @jensmaurer
    Member

    I think it makes sense to have "when-needed" treatment for default member initializers.

  4. t3nsor commented on Dec 20, 2025

    @t3nsor

    There has been some prior discussion of this topic at https://lists.isocpp.org/core/2025/02/17366.php

    A way of looking at my previously expressed views is that, just like a function, a default member initializer has both a declaration and a definition that can be instantiated when needed. Instantiating the enclosing class does neither. Using the default member initializer only from an unevaluated context instantiates the declaration, which has a similar effect to instantiating a decltype-specifier in a function's declared return type. Using the default member initializer from a potentially-evaluated context instantiates the definition, which means that everything referenced by it is odr-used if it would be if the same expression appeared in a function body.

  5. ranaanoop commented on Dec 21, 2025

    @ranaanoop
    Author

    @t3nsor Yes, exactly as also said in the suggested resolution of this issue. I think what we suggested as resolution is also the most expected behavior and would not confuse people. That is, if the default mem-initializer behaves same as the non-static member function in this instantiation context, then that would be avoid any surprise and so would be the expected behavior.

  6. ranaanoop commented on Feb 11, 2026

    @ranaanoop
    Author

    @jensmaurer Can we have a CWG issue for this. Suggested resolution can be what @t3nsor suggested.

  7. jensmaurer commented on Feb 19, 2026

    @jensmaurer
    Member

    @ranaanoop , care to provide actual wording diffs against the current standard to implement this?

  8. t3nsor commented on Feb 20, 2026

    @t3nsor

    Maybe something like this...

    Edit [temp.inst]/11, and maybe add a line break:

    An implementation shall not implicitly instantiate a function template, a variable template, a member template, a non-virtual member function, a member class or static data member of a templated class, or a substatement of a constexpr if statement ([stmt.if]), unless such instantiation is required.

    [Note 5: The instantiation of a generic lambda does not require instantiation of substatements of a constexpr if statement within its compound-statement unless the call operator template is instantiated. — end note]

    It is unspecified whether or not an implementation implicitly instantiates a virtual member function of a class template if the virtual member function would not otherwise be instantiated. The use of a template specialization in a default argument or default member initializer shall not cause the template to be implicitly instantiated except where needed to determine the correctness of the default argument or default member initializer. The use of a default argument in a function call causes specializations in the default argument to be implicitly instantiated. Similarly, the use of a default member initializer in a constructor definition or an aggregate initialization causes specializations in the default member initializer to be instantiated.

    Let I be a default argument or default member initializer for a parameter or member, respectively, of type T, considering I to include the initialization of the parameter or member from the initializer-clause or brace-or-equal-initializer, respectively. Each templated entity X referenced by I is instantiated if needed to determine the correctness of I. Additionally, for the purposes of determining whether a definition of X is required to exist or whether the existence of a definition for X affects the semantics of the program, I is considered to be a templated function F having the form T F() { return T I; }, and a function call, constructor definition or aggregate initialization that uses I is considered to call F. [Note: Whether X is instantiated can therefore depend on whether the definition of F is instantiated, which in turn can depend on whether a function call or aggregate initialization that uses I is potentially-evaluated. This rule applies even if I is not itself templated.]

  9. t3nsor commented on Mar 31, 2026

    @t3nsor

    @jensmaurer thoughts?

  10. jensmaurer commented on Apr 16, 2026

    @jensmaurer
    Member

    CWG3175

    Although I believe the proposed wording doesn't address the example at the top of this issue, because Wrapper is not templated, so it's not an "X" covered by the wording.

  11. changed the title [-][temp.inst] Clarify if initializer is needed when implicitly instantiating a non-static data member that is not used in constexpr context[/-] [+]CWG3175 [temp.inst] Clarify if initializer is needed when implicitly instantiating a non-static data member that is not used in constexpr context[/+] on Apr 16, 2026
  12. t3nsor commented on Apr 16, 2026

    @t3nsor

    True, accepting that program would require us to make default member initializers also separately instantiated constructs, which I suppose we could do, but seems a bit evolutionary.

  13. t3nsor commented on Apr 17, 2026

    @t3nsor

    Putting this here in case I forget. In non-template code:

    • No implementation requires a definition of a non-consteval function that is referenced from a default member initializer if that default member initializer is used only from unevaluated contexts. This suggests that they do not treat the reference as an odr-use (though, as we all know, ODR violations are not required to be diagnosed).
    • Clang and GCC do not require a definition for a consteval function that is called in a default member initializer if that default member initializer is used only from unevaluated contexts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions