Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

gcc and clang implicitly instantiate template arguments during operator overload resolution

Consider this code:

struct A; // incomplete type

template<class T>
struct D { T d; };

template <class T>
struct B { int * p = nullptr; };

int main() {
    B<D<A>> u, v;
    u = v;  // doesn't compile; complain that D<A>::d has incomplete type
    u.operator=(v); // compiles
}

Demo. Since u.operator=(v) compiles but u = v; doesn't, somewhere during the overload resolution for the latter expression the compiler must have implicitly instantiated D<A> - but I don't see why that instantiation is required.

To make things more interesting, this code compiles:

struct A; // incomplete type

template<class T>
struct D; // undefined

template <class T>
struct B { int * p = nullptr; };

int main() {
    B<D<A>> u, v;
    u = v;
    u.operator=(v);
}

Demo.

What's going on here? Why does u = v; cause the implicit instantiation of D<A> - a type that's nowhere used in the body of B's definition - in the first case but not the second?

like image 539
T.C. Avatar asked Sep 19 '14 02:09

T.C.


1 Answers

The entire point of the matter is ADL kicking in:

N3797 - [basic.lookup.argdep]

When the postfix-expression in a function call (5.2.2) is an unqualified-id, other namespaces not considered during the usual unqualified lookup (3.4.1) may be searched, and in those namespaces, namespace-scope friend function or function template declarations (11.3) not otherwise visible may be found.

following:

For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. [...] The sets of namespaces and classes are determined in the following way:

  • If T is a class type [..] its associated classes are: ... furthemore if T is a class template specialization its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters

D<A> is an associated class and therefore in the list waiting for its turn.

Now for the interesting part [temp.inst]/1

Unless a class template specialization has been explicitly instantiated (14.7.2) or explicitly specialized (14.7.3), the class template specialization is implicitly instantiated [...] when the completeness of the class type affects the semantics of the program

One could think that the completeness of the type D<A> doesn't affect at all the semantic of that program, however [basic.lookup.argdep]/4 says:

When considering an associated namespace, the lookup is the same as the lookup performed when the associated namespace is used as a qualifier (3.4.3.2) except that:

[...] Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3)

i.e. the completeness of the class type actually affects friends declarations -> the completeness of the class type therefore affects the semantics of the program. That is also the reason why your second sample works.

TL;DR D<A> is instantiated.

The last interesting point regards why ADL starts in the first place for

u = v; // Triggers ADL
u.operator=(v); // Doesn't trigger ADL

§13.3.1.2/2 dictates that there can be no non-member operator= (other than the built-in ones). Join this to [over.match.oper]/2:

The set of non-member candidates is the result of the unqualified lookup of operator@ in the context of the expression according to the usual rules for name lookup in unqualified function calls (3.4.2) except that all member functions are ignored.

and the logical conclusion is: there's no point in performing the ADL lookup if there's no non-member form in table 11. However [temp.inst]p7 relaxes this:

If the overload resolution process can determine the correct function to call without instantiating a class template definition, it is unspecified whether that instantiation actually takes place.

and that's the reason why clang triggered the entire ADL -> implicit instantiation process in the first place.

Starting from r218330 (at the time of writing this, it has been committed a few minutes ago) this behavior was changed not to perform ADL for operator= at all.


References

  • r218330
  • clang sources / Sema module
  • cfe-dev
  • N3797

Thanks to Richard Smith and David Blaikie for helping me figuring this out.

like image 101
Marco A. Avatar answered Oct 16 '22 20:10

Marco A.