When is a class template depending on a incomplete type as a template argument instantiated?

Viewed 65
class I;

template<class T>
struct P
{
    T t;
};

template<class T>
struct F
{
    operator bool() {return false;}
};


int main()
{
    int a = F<P<I>>();
    int b = !F<P<I>>();
    int c = int(F<P<I>>());
    int d = !int(F<P<I>>());
}

Are the evaluations in the initializers above well-formed? Why or why not? Which rules in the standard mandate the behavior?

1 Answers

Specializations are instantiated only if a complete type is required or affects the semantics in the given context. [temp.inst]/2

So the default behavior is not to do any implicit instantiation if not necessary.

In all the cases you have shown, the specializations of F need to be instantiated because T() requires T to be complete. However, nothing in the instantiation of F<P<I>> requires P<I> to be complete, so it will not be instantiated with it.

None of the conversions to int require P<I> to be complete either and so the initializations of a, c and d don't cause any instantiation of P<I> at all and they are well-formed.

However, for b the situation is a bit different. In order to determine which operator! overload to call, unqualified name lookup and argument-dependent name lookup of operator! is done.

Argument-dependent lookup looks for operator! declarations in multiple scopes related to the type F<P<I>>.

First, for F itself, it includes F's class scope and the enclosing namespace scope, meaning the global scope. However, secondly, it also includes the class- and enclosing namespace scope of types in the template arguments of the type, meaning that it includes P<I>'s class scope as well.

In order to determine whether there is an operator! overload in P<I>, it must be instantiated. While instantiating P<I> the declaration T t; is also instantiated as I t;, which requires I to be complete, but which it isn't, making the initialization of b ill-formed.


However [temp.inst]/9 allows, but doesn't require, the instantiation to be skipped if it isn't needed to determine the result of overload resolution. I am not sure how wide this permission is supposed to be interpreted, but if it does apply here, then it is unspecified whether instantiation of P<I> for the initialization of b happens.

Related