Igor G
Igor G

Reputation: 2371

An invalid default member initializer that is never used

Please consider the following code:

template <typename T>
struct Test
{
    Test() = default;

    explicit Test(const T& arg)
     : m_member(arg)
    {
    }

    T     m_member{};
};

int main()
{
    Test<int>       t1;

    int             v2 = 34;
    Test<int&>      t2(v2);     // (!)

    return 0;
}

Should the code above compile and exhibit no undefined behavior?

The line marked (!) instantiates the class template Test with a parameter of reference type. In this case, the default initializer for member Test::m_member is invalid (well, a reference must be initialized with some object). But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.

Under C++11, is the compiler allowed/required to attempt/skip the instantiation of a default initializer for a member which is not used in the instantiated constructors (i.e. the program doesn't instantiate any of the constructors which could require that initializer)?

Upvotes: 4

Views: 264

Answers (3)

YSC
YSC

Reputation: 40080

Fortunately, this is OK.

But on the other hand, the default constructor (the only one which could have used that default initializer) is never used in the program, and so it shouldn't be instantiated.

Indeed:

[temp.mem.func]/2

The template-arguments for a member function of a class template are determined by the template-arguments of the type of the object for which the member function is called.

Since in your example, Test<int&>::Test() is not called, it's template-arguments are not determined and its construct doesn't make the program ill-formed.

Upvotes: 4

This is described in CWG Issue 1396

Deferred instantiation and checking of non-static data member initializers

Section: 17.8.1 [temp.inst] Status: drafting Submitter: Jason Merrill Date: 2011-09-22

Non-static data member initializers get the same late parsing as member functions and default arguments, but are they also instantiated as needed like them? And when is their validity checked?

Notes from the October, 2012 meeting:

CWG agreed that non-static data member initializers should be handled like default arguments.

As you see, the consensus was that default member initializers be treated like default arguments to functions. So we can examine the behavior for default arguments to determine how the CWG intends for this to be treated. We can see that those aren't instantiated along with the class definition:

[temp.inst]/1

The implicit instantiation of a class template specialization causes the implicit instantiation of the declarations, but not of the definitions or default arguments, of the class member functions, member classes, scoped member enumerations, static data members and member templates;

And that they would be instantiated if a constructor was called in a way that used them, same as a member function f in this paragraph

[temp.inst]/12

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope, the same template parameters and the same access as that of the function template f used at that point. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f.

Upvotes: 2

Ted Lyngmo
Ted Lyngmo

Reputation: 117308

Should the code above compile and exhibit no undefined behavior?

Yes, I can't see any reason why it shouldn't.

Under C++11, is the compiler allowed/required to attempt/skip the instantiation of a default initializer for a member which is not used in the instantiated constructors (i.e. the program doesn't instantiate any of the constructors which could require that initializer)?

I don't have the C++11 spec but the current draft says:

[default.ctor-2.3]

A defaulted default constructor for class X is defined as deleted if:
any non-static data member with no default member initializer ([class.mem]) is of reference type,

Since you do have a default member initializer (T m_member{};) it will not delete your defaulted default constructor in case you try to use it when T is a reference type.

It'll instead fail compiling:
All output is from using -std=c++11

g++: error: cannot bind non-const lvalue reference of type ‘int&’ to an rvalue of type ‘int’
clang++ error: non-const lvalue reference to type 'int' cannot bind to an initializer list temporary

Trying to fix it by removing the default member initializer:

   11 |     T     m_member;

Results in the deletion required:

21:16: error: use of deleted function ‘Test<T>::Test() [with T = int&]’
   21 |     Test<int&> x;
      |                ^
4:5: note: ‘Test<T>::Test() [with T = int&]’ is implicitly deleted because the default definition would be ill-formed:
    4 |     Test() = default;
      |     ^~~~
4:5: error: uninitialized reference member in ‘struct Test<int&>’

Upvotes: 0

Related Questions