I have the following C++ code:
//Define to 1 to make it work
#define WORKS 0
#if WORKS
template< typename T > struct Foo;
#else
template< typename T >
struct Foo {
T t;
};
#endif
class Bar; //Incomplete type
void fFooBar(Foo<Bar> const & foobar) { }
void f(Foo<Bar> const & foobar) {
fFooBar(foobar);
}
int main() {
return 0;
}
If WORKS is defined as 0 (the struct template is defined) the code doesn't compile, because it tries to instantiate it at fFooBar(foobar);
and fails, because Bar
is incomplete.
If WORKS is defined as 1 (the struct template is undefined) the code compiles.
According to the standard, a template should not be instatiated, unless a complete type is required (which is not the case for const&
) or it would alter the semantics of the code (which again isn't the case, and againt, the same should happen if the template was undefined).
Also, it's weird that the program can be made to compile by removing information from the compilation unit. But the fact that MSVC, gcc, and clang all do the same makes me think there must be a reason behind this.
When WORKS=0
, the program can be made to compile in Clang by qualifying the call to fFooBar
with ::
. The standard requires that name lookup behaves differently when an unqualified name is used in a function call.
[basic.lookup.argdep]/1
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 declarations (11.3) not otherwise visible may be found.
Examining the (somewhat complex) rules for the Argument Dependent Lookup process suggests that it can only be implemented correctly in a way that would require the instantiation of template specialisations in the types of the arguments to the call.
[basic.lookup.argdep]/2
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 is determined entirely by the types of the function arguments [...]
- If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes.
One interpretation of this is that a class is required to be complete if it is used in the type of an argument to an unqualified function call. An alternative interpretation is that ADL should only cause instantiation of templates that are complete.
Either behaviour is compliant with the standard according to Working Draft N3337
[temp.inst]/6
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.
template <class T> struct S {
operator int();
};
void f(int);
void f(S<int>&);
void f(S<float>);
void g(S<int>& sr) {
f(sr); // instantiation of S<int> allowed but not required
// instantiation of S<float> allowed but not required
};
[temp.inst]/7
If an implicit instantiation of a class template specialization is required and the template is declared but not defined, the program is ill-formed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With