gcc and clang implicitly instantiate template arguments during operator overload resolution - c++

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?

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.

Well, I think in Visual Studio 2013 code should look like(without = nullptr):
struct A; // incomplete type
template<class T>
struct D { T d; };
template <class T>
struct B { int * p; };
int void_main() {
B<D<A>> u, v;
u = v; // compiles
u.operator=(v); // compiles
return 0;
}
In this case it should compile well just because incomplete types can be used for specific template class specialization usage.
As for the run-time error - the variable v used without initialization - it's correct - struct B does not have any constructor => B::p is not initialized and could contain garbage.

Related

Behaviour of friend function template returning deduced dependent type in class template

I've happened across the following code, the behaviour of which is diagreed upon by all of GCC, Clang, and MSVC:
#include <concepts>
template<typename T>
auto foo(T);
template<typename U>
struct S {
template<typename T>
friend auto foo(T) {
return U{};
}
};
S<double> s;
static_assert(std::same_as<decltype(foo(42)), double>);
Live demo: https://godbolt.org/z/hK6xhesKM
foo() is declared at global namespace with deduced return type. S<U> provides a definition for foo() via a friend function, where it returns a value of type U.
What I expected is that when S is instantiated with U=double, its definition of foo() gets put in the global namespace and U is substituted in, due to how friend functions work, effectively like this:
template<typename T>
auto foo(T);
S<double> s;
template<typename T>
auto foo(T) {
return double{};
}
Thus I expect foo()'s return type is double, and the following static assertion should pass:
static_assert(std::same_as<decltype(foo(42)), double>);
However, what actually happens is that all three compilers disagree on the behaviour.
GCC passes the static assertion, like I expect.
Clang fails the static assertion, as it seems to believe that foo() returns int:
'std::is_same_v<int, double>' evaluated to false
And MSVC produces a different error entirely:
'U': undeclared identifier
I find it bizarre that all the compilers have different behaviour here for a seemingly simple code example.
The issue does not occur if foo() isn't templated (see demo here), or if it doesn't have deduced return type (see demo here).
Which compiler has the correct behaviour? Or, is the code ill-formed NDR or undefined behaviour? (And, why?)
Which compiler has the correct behaviour? Or, is the code ill-formed NDR or undefined behaviour? (And, why?)
As #Sedenion points out in a comment, whilst touching upon the domain of CWG 2118 (we'll return to this further down) this program is by the current standard well-formed and GCC is correct to accept it, whereas Clang and MSVC are incorrect to reject it, as governed by [dcl.spec.auto.general]/12 and /13 [emphasis mine]:
/12 Return type deduction for a templated entity that is a function or function template with a placeholder in its declared type
occurs when the definition is instantiated even if the function body contains a return statement with a non-type-dependent operand.
/13 Redeclarations or specializations of a function or function template
with a declared return type that uses a placeholder type shall also
use that placeholder, not a deduced type. Similarly, redeclarations or
specializations of a function or function template with a declared
return type that does not use a placeholder type shall not use a
placeholder.
[Example 6:
auto f();
auto f() { return 42; } // return type is int
auto f(); // OK
// ...
template <typename T> struct A {
friend T frf(T);
};
auto frf(int i) { return i; } // not a friend of A<int>
Particularly the "not a friend of A<int>" example of /13 highlights that the redeclaration shall use a placeholder type (frf(T), and not a deduced type (frf(int)), whereas otherwise that particular example would be valid.
/12, along with [temp.inst]/3 (below) covers the that return type deduction for the friend occurs only after the friend's primary template definition has been made available (formally: it's declaration but not definition has been instantiated) from the instantiation of the S<double> enclosing class template specialization.
The implicit instantiation of a class template specialization causes
(3.1) 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
[...]
However, for the purpose of determining whether an instantiated
redeclaration is valid according to [basic.def.odr] and [class.mem], a
declaration that corresponds to a definition in the template is
considered to be a definition.
[Example 4:
// ...
template<typename T> struct Friendly {
template<typename U> friend int f(U) { return sizeof(T); }
};
Friendly<char> fc;
Friendly<float> ff; // error: produces second definition of f(U)
— end example]
CWG 2118: Stateful metaprogramming via friend injection
As covered in detail e.g. in A foliage of folly, one can rely on the single first instantiation of a class template to control how the definition of a friend looks like. First here means essentially relying on meta-programming state to specify this definition.
In OP's example the first instantiation, S<double>, is used to set the definition of the primary template of the friend foo such that its deduced type will always deduce to double, for all specializations of the friend. If we ever (in the whole program) instantiate, implicitly or explicitly, a second instantiation of S (ignoring S being specialized to remove the friend), we run into ODR-violations and undefined behavior. This means that this kind of program is, in practice, essentially useless, as it would serve clients undefined behavior on a platter, but as covered in the article linked to above it can be used for utility classes to circumvent private access rules (whilst still being entirely well-formed) or other hacky mechanisms such as stateful metaprogramming (which typically runs into or beyond the grey area of well-formed).

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

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

Why doesn't function declared inside other function participate in argument dependent lookup?

Consider a simple example:
template <class T>
struct tag { };
int main() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{}); // <- compiles OK
foo(tag<int>{}); // 'bar' was not declared in this scope ?!
}
tag<int> bar(tag<int>) { return {}; }
Both [gcc] and [clang] refuses to compile the code. Is this code ill-formed in some way?
foo(tag<int>{}); triggers the implicit instantiation of a specialization of the function call operator member function template of foo's closure type with the template argument tag<int>. This creates a point of instantiation for this member function template specialization. According to [temp.point]/1:
For a function template specialization, a member function template
specialization, or a specialization for a member function or static
data member of a class template, if the specialization is implicitly
instantiated because it is referenced from within another template
specialization and the context from which it is referenced depends on
a template parameter, the point of instantiation of the specialization
is the point of instantiation of the enclosing specialization.
Otherwise, the point of instantiation for such a specialization
immediately follows the namespace scope declaration or definition
that refers to the specialization.
(emphasis mine)
So, the point of instantiation is immediately after main's definition, before the namespace-scope definition of bar.
Name lookup for bar used in decltype(bar(x)) proceeds according to [temp.dep.candidate]/1:
For a function call where the postfix-expression is a dependent
name, the candidate functions are found using the usual lookup rules
(6.4.1, 6.4.2) except that:
(1.1) — For the part of the lookup using unqualified name lookup
(6.4.1), only function declarations from the template definition
context are found.
(1.2) — For the part of the lookup using associated namespaces
(6.4.2), only function declarations found in either the template
definition context or the template instantiation context are found. [...]
Plain unqualified lookup in the definition context doesn't find anything. ADL in the definition context doesn't find anything either. ADL in the instantiation context, according to [temp.point]/7:
The instantiation context of an expression that depends on the
template arguments is the set of declarations with external linkage
declared prior to the point of instantiation of the template
specialization in the same translation unit.
Again, nothing, because bar hasn't been declared at namespace scope yet.
So, the compilers are correct. Moreover, note [temp.point]/8:
A specialization for a function template, a member function template,
or of a member function or static data member of a class template may
have multiple points of instantiations within a translation unit, and
in addition to the points of instantiation described above, for any
such specialization that has a point of instantiation within the
translation unit, the end of the translation unit is also considered
a point of instantiation. A specialization for a class template has
at most one point of instantiation within a translation unit. A
specialization for any template may have points of instantiation in
multiple translation units.
If two different points of instantiation give a template specialization different meanings according to the one-definition rule
(6.2), the program is ill-formed, no diagnostic required.
(emphasis mine)
and the second part of [temp.dep.candidate]/1:
[...] If the call would be ill-formed or would find a better match had
the lookup within the associated namespaces considered all the
function declarations with external linkage introduced in those
namespaces in all translation units, not just considering those
declarations found in the template definition and template
instantiation contexts, then the program has undefined behavior.
So, ill-formed NDR or undefined behavior, take your pick.
Let's consider the example from your comment above:
template <class T>
struct tag { };
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
tag<int> bar(tag<int>) { return {}; }
int main() {
auto foo = build();
foo(tag<int>{});
}
Lookup in the definition context still doesn't find anything, but the instantiation context is immediately after main's definition, so ADL in that context finds bar in the global namespace (associated with tag<int>) and the code compiles.
Let's also consider AndyG's example from his comment above:
template <class T>
struct tag { };
//namespace{
//tag<int> bar(tag<int>) { return {}; }
//}
auto build() {
auto foo = [](auto x) -> decltype(bar(x)) { return {}; };
return foo;
}
namespace{
tag<int> bar(tag<int>) { return {}; }
}
int main() {
auto foo = build();
foo(tag<int>{});
}
Again, the instantiation point is immediately after main's definition, so why isn't bar visible? An unnamed namespace definition introduces a using-directive for that namespace in its enclosing namespace (the global namespace in this case). This would make bar visible to plain unqualified lookup, but not to ADL according to [basic.lookup.argdep]/4:
When considering an associated namespace, the lookup is the same as
the lookup performed when the associated namespace is used as a
qualifier (6.4.3.2) except that:
(4.1) — Any using-directives in the associated namespace are
ignored. [...]
Since only the ADL part of the lookup is performed in the instantiation context, bar in the unnamed namespace is not visible.
Commenting out the lower definition and uncommenting the upper one makes bar in the unnamed namespace visible to plain unqualified lookup in the definition context, so the code compiles.
Let's also consider the example from your other comment above:
template <class T>
struct tag { };
int main() {
void bar(int);
auto foo = [](auto x) -> decltype(bar(decltype(x){})) { return {}; };
tag<int> bar(tag<int>);
bar(tag<int>{});
foo(tag<int>{});
}
tag<int> bar(tag<int>) { return {}; }
This is accepted by GCC, but rejected by Clang. While I was initially quite sure that this is a bug in GCC, the answer may actually not be so clear-cut.
The block-scope declaration void bar(int); disables ADL according to [basic.lookup.argdep]/3:
Let X be the lookup set produced by unqualified lookup (6.4.1) and let
Y be the lookup set produced by argument dependent lookup (defined as
follows). If X contains
(3.1) — a declaration of a class member, or
(3.2) — a block-scope function declaration that is not a
using-declaration, or
(3.3) — a declaration that is neither a function nor a function
template
then Y is empty. [...]
(emphasis mine)
Now, the question is whether this disables ADL in both the definition and instantiation contexts, or only in the definition context.
If we consider ADL disabled in both contexts, then:
The block-scope declaration, visible to plain unqualified lookup in the definition context, is the only one visible for all instantiations of the closure type's member function template specializations. Clang's error message, that there's no viable conversion to int, is correct and required - the two quotes above regarding ill-formed NDR and undefined behavior don't apply, since the instantiation context doesn't influence the result of name lookup in this case.
Even if we move bar's namespace-scope definition above main, the code still doesn't compile, for the same reason as above: plain unqualified lookup stops when it finds the block-scope declaration void bar(int); and ADL is not performed.
If we consider ADL disabled only in the definition context, then:
As far as the instantiation context is concerned, we're back to the first example; ADL still can't find the namespace-scope definition of bar. The two quotes above (ill-formed NDR and UB) do apply however, and so we can't blame a compiler for not issuing an error message.
Moving bar's namespace-scope definition above main makes the code well-formed.
This would also mean that ADL in the instantiation context is always performed for dependent names, unless we have somehow determined that the expression is not a function call (which usually involves the definition context...).
Looking at how [temp.dep.candidate]/1 is worded, it seems to say that plain unqualified lookup is performed only in the definition context as a first step, and then ADL is performed according to the rules in [basic.lookup.argdep] in both contexts as a second step. This would imply that the result of plain unqualified lookup influences this second step as a whole, which makes me lean towards the first option.
Also, an even stronger argument in favor of the first option is that performing ADL in the instantiation context when either [basic.lookup.argdep]/3.1 or 3.3 apply in the definition context doesn't seem to make sense.
Still... it may be worth asking about this one on std-discussion.
All quotes are from N4713, the current standard draft.
From unqualified lookup rules ([basic.lookup.unqual]):
For the members of a class X, a name used in a member function body, [...], shall be declared in one of the
following ways
— if X is a local class or is a nested class of a local class, before the definition of class X in a block
enclosing the definition of class X
Your generic lambda is a local class within main, so to use bar, the name bar must appear in a declaration beforehand.

Namespace causes sub-optimal template overload resolution

This is very similar to this question, but I'm not sure the answer there is entirely applicable to the minimal code I've put together that demonstrates the issue. (My code does not use trailing-return types, and there are some other differences as well.) Additionally, the issue of whether MSVC's behavior is legal doesn't seem to be addressed.
In short, I'm seeing the compiler select a generic function template instantiation rather than a more-specific overload when the function template is inside a namespace.
Consider the following set of namespace and class definitions:
namespace DoStuffUtilNamespace
{
template<typename UNKNOWN>
void doStuff(UNKNOWN& foo)
{
static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!");
}
}
class UtilForDoingStuff
{
public:
template <typename UNKNOWN>
void doStuffWithObjectRef(UNKNOWN& ref)
{
DoStuffUtilNamespace::doStuff(ref);
}
};
class MyClassThatCanDoStuff { };
namespace DoStuffUtilNamespace
{
using ::MyClassThatCanDoStuff; // No effect.
void doStuff(MyClassThatCanDoStuff& foo) { /* No assertion! */ }
}
... and the following use-cases:
int main()
{
MyClassThatCanDoStuff foo;
DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo;
UtilForDoingStuff util;
DoStuffUtilNamespace::doStuff(foo); // Compiles
DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles
util.doStuffWithObjectRef(foo); // Triggers static assert
util.doStuffWithObjectRef(scoped_foo); // Triggers static assert
}
If the entire DoStuffUtilNamespace is eliminated and all its members are moved to global scope, this compiles fine with G++ and Clang++.
With the namespace, doStuff is of course a dependent name. According to the top-voted answer on the similar question, the standard says:
In resolving dependent names, names from the following sources are considered:
Declarations that are visible at the point of definition of the template.
Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.
This seems a little odd to me; I don't understand why the first bullet point would specify that the declarations must be visible at the point of definition of the template rather than at the point of instantiation, since the second bullet point explicitly specifies that some declarations visible only at the point of instantiation are allowed. (If someone would like to offer a rationale, I'd appreciate it, but that's not my question because it's my understanding that questions of the form "why did the standards committee decide X" are off topic.)
So I think that explains why util.doStuffWithObjectRef(foo); triggers the static assertion: doStuff(MyClassThatCanDoStuff&) hasn't been declared at the point of definition of UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&). And indeed moving the class UtilForDoingStuff definition after the doStuff overload has been defined seems to fix the issue.
But what exactly does the standard mean by "namespaces associated with the types of the function arguments"? Shouldn't the using ::MyClassThatCanDoStuff declaration, together with the explicit scoping of the scoped_foo instance type within the namespace, trigger argument-dependent lookup, and shouldn't this look-up find the non-asserting definition of doStuff()?
Also, the entire code is compiled without error using clang++ -ftemplate-delayed-parsing, which emulates MSVC's template-parsing behavior. This seems preferable, at least in this particular case, because the ability to add new declarations to a namespace at any time is one of the primary appeals of namespaces. But, as noted above, it doesn't quite seem to follow the letter of the law, according to the standard. Is it permissible, or is it an instance of non-conformance?
EDIT:: As pointed out by KIIV, there is a workaround; the code compiles if template specialization is used instead of overloading. I would still like to know the answers to my questions about the standard.
With the namespace, doStuff is of course a dependent name.
You are starting from the wrong premise. There is no ADL for a qualified call like DoStuffUtilNamespace::doStuff(ref). [basic.lookup.argdep]/p1, emphasis mine:
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.
DoStuffUtilNamespace::doStuff is a qualified-id, not an unqualified-id. ADL doesn't apply.
For this reason, DoStuffUtilNamespace::doStuff is also not a dependent name. [temp.dep]/p1:
In an expression of the form:
postfix-expression ( expression-listopt)
where the postfix-expression is an unqualified-id, the
unqualified-id denotes a dependent name if [...]. If an operand of an operator is a type-dependent expression, the operator also denotes
a dependent name. Such names are unbound and are looked up at the
point of the template instantiation (14.6.4.1) in both the context of
the template definition and the context of the point of instantiation
(The italicization of dependent name indicate that this paragraph is defining the term.)
Instead, per [temp.nondep]/p1:
Non-dependent names used in a template definition are found using the
usual name lookup and bound at the point they are used.
which doesn't find your later overload declaration.
Specialization works because it's still the same function template declaration that's used; you just supplied a different implementation than the default one.
But what exactly does the standard mean by "namespaces associated with
the types of the function arguments"? Shouldn't the using ::MyClassThatCanDoStuff declaration, together
with the explicit scoping of the scoped_foo instance type within the
namespace, trigger argument-dependent lookup
No. using-declarations do not affect ADL. [basic.lookup.argdep]/p2, emphasis mine:
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
(and the namespace of any template template argument).
Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are
determined in the following way:
If T is a fundamental type, [...]
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. Its associated namespaces are the
innermost enclosing namespaces of its associated classes. Furthermore,
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
(excluding template template parameters); the namespaces of which any
template template arguments are members; and the classes of which any
member templates used as template template arguments are members. [
Note: Non-type template arguments do not contribute to the set of associated namespaces. —end note ]
[...]
With template specialization I can get it work:
namespace DoStuffUtilNamespace
{
template<typename UNKNOWN>
void doStuff(UNKNOWN& foo)
{
static_assert(sizeof(UNKNOWN) == -1, "CANNOT USE DEFAULT INSTANTIATION!");
}
}
class UtilForDoingStuff
{
public:
template <typename UNKNOWN>
void doStuffWithObjectRef(UNKNOWN& ref)
{
DoStuffUtilNamespace::doStuff(ref);
}
};
class MyClassThatCanDoStuff { };
namespace DoStuffUtilNamespace
{
using ::MyClassThatCanDoStuff;
template <> void doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo) { /* No assertion! */ }
}
int main()
{
MyClassThatCanDoStuff foo;
DoStuffUtilNamespace::MyClassThatCanDoStuff scoped_foo;
UtilForDoingStuff util;
DoStuffUtilNamespace::doStuff(foo); // Compiles
DoStuffUtilNamespace::doStuff(scoped_foo); // Compiles
util.doStuffWithObjectRef(foo); // Compiles
util.doStuffWithObjectRef(scoped_foo); // Compiles
}
Declarations from namespaces associated with the types of the function arguments both from the instantiation context and from the definition context.
Example with the following code which prints B::foo Demo
namespace A
{
template <typename T>
void foo(const T&) {std::cout << "A::foo" << std::endl;}
template <typename T>
void bar(const T& t) {
foo(t); // thank to ADL, it will also look at B::foo for B::S.
}
}
namespace B
{
struct S {};
void foo(const S&) {std::cout << "B::foo" << std::endl;}
}
int main()
{
B::S s;
A::bar(s);
}
So when calling ?::foo(const B::S&), the second bullet point adds B::foo to the list of overloads.
why template-specialization works in this case
There is only one function:
template<>
void DoStuffUtilNamespace::doStuff<MyClassThatCanDoStuff>(MyClassThatCanDoStuff& foo);
even if it is defined later.
Note that the fact that there is a specialization should be known in the translation unit, else the program is ill formed (doesn't respect ODR).
while overloading doesn't.
You think:
So I think that explains why util.doStuffWithObjectRef(foo); triggers the static assertion: doStuff(MyClassThatCanDoStuff&) hasn't been declared at the point of definition of UtilForDoingStuff::doStuffWithObjectRef<UNKNOWN>(UNKNOWN&). And indeed moving the class UtilForDoingStuff definition after the doStuff overload has been defined seems to fix the issue.
Exactly.

Class member visibility in member function declaration signature

Why does this work:
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
But this does not (a and f swapped places):
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
saying that a is not declared in that scope (inside decltype) but adding explicit this-> makes it work.
template <typename A>
struct S {
A a;
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
};
This works because within a trailing return type, members of the surrounding class are visible. Not all members, but only the members that are declared prior to it (in a trailing return type, the class is not considered to be complete, as opposed to function bodies). So what is done here:
As we are in a template, a lookup is done to see whether a is dependent or not. Since a was declared prior to f, a is found to refer to a class member.
By the template rules in C++, it is found that a refers to a member of the current instantiation since it is a member of instantiations of the surrounding template. In C++, this notion is used mainly to decide whether names are dependent: If a name is known to refer to the surrounding template's members, it is not necessarily needed to be looked up when instantiating, because the compiler already knows the code of the template (which is used as the basis of the class type instantiated from it!). Consider:
template<typename T>
struct A {
typedef int type;
void f() {
type x;
A<T>::type y;
}
};
In C++03, the second line declaring y would be an error, because A<T>::type was a dependent name and needed a typename in front of it. Only the first line was accepted. In C++11, this inconsistency was fixed and both type names are non-dependent and won't need a typename. If you change the typedef to typedef T type; then both declarations, x and y will use a dependent type, but neither will need a typename, because you still name a member of the current instantiation and the compiler knows that you name a type.
So a is a member of the current instantiation. But it is dependent, because the type used to declare it (A) is dependent. However this doesn't matter in your code. Whether dependent or not, a is found and the code valid.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b))
{
}
A a;
};
In this code, again a is looked up to see whether it is dependent and/or whether it is a member of the current instantiation. But since we learned above that members declared after the trailing return type are not visible, we fail to find a declaration for a. In C++, besides the notion "member of the current instantiation", there is another notion:
member of an unknown specialization. This notion is used to refer to the case where a name might instead refer to a member of a class that depends on template parameters. If we had accessed B::a, then the a would be a member of an unknown specialization because it is unknown what declarations will be visible when B is substituted at instantiation.
neither a member of the current, nor a member of an unknown specialization. This is the case for all other names. Your case fits here, because it is known that a can never be a member of any instantiation when instantiation happens (remember that name lookup cannot find a, since it is declared after f).
Since a is not made dependent by any rule, the lookup that did not find any declaration is binding, meaning there is no other lookup at instantiation that could find a declaration. Non-dependent names are lookup up at template definition time. Now GCC rightfully gives you an error (but note that as always, an ill-formed template is not required to be diagnosed immediately).
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(this->a.f(b))
{
}
A a;
};
In this case, you added this and GCC accepted. The name a that follows this-> again is lookup at to see whether it might be a member of the current instantiation. But again because of the member visibility in trailing return types, no declaration is found. Hence the name is deemed not to be a member of the current instantiation. Since there is no way that at instantiation, S could have additional members that a could match (there are no base classes of S that depend on template parameters), the name is also not a member of an unknown specialization.
Again C++ has no rules to make this->a dependent. However it uses this->, so the name must refer to some member of S when it is instantiated! So the C++ Standard says
Similarly, if the id-expression in a class member access expression for which the type of the object expression is the current instantiation does not refer to a member of the current instantiation or a member of an unknown specialization, the program is ill-formed even if the template containing the member access expression is not instantiated; no diagnostic required.
Again no diagnostic is required for this code (and GCC actually doesn't give it). The id-expression a in the member access expression this->a was dependent in C++03 because the rules in that Standard were not as elaborated and fine-tuned as in C++11. For a moment let's imagine C++03 had decltype and trailing return types. What would this mean?
The lookup would have been delayed until instantiation, because this->a would be dependent
The lookup at instantiation of, say, S<SomeClass> would fail, because this->a would not be found at instantiation time (as we said, trailing return types do not see members declared later).
Hence, the early rejection of that code by C++11 is good and useful.
The body of a member function is compiled as if it was defined after the class. Therefore everything declared in the class is in scope at that point.
However, the declaration of the function is still inside the class declaration and can only see names that precede it.
template <typename A>
struct S {
template <typename B>
auto f(B b) ->
decltype(a.f(b)); // error - a is not visible here
A a;
};
template <typename A>
template <typename B>
auto S<A>::f(B b) ->
decltype(a.f(b))
{
return a.f(b); // a is visible here
}
The Standard says (section 14.6.2.1):
If, for a given set of template arguments, a specialization of a template is instantiated that refers to a member of the current instantiation with a qualified-id or class member access expression, the name in the qualified-id or class member access expression is looked up in the template instantiation context.
this->a is a class-member access expression, therefore this rule applies and lookup takes place at the point of instantiation, where S<A> is complete.
Finally, this doesn't solve your problem at all, because section 5.1.1 says:
If a declaration declares a member function or member function template of a class X, the expression this is a prvalue of type “pointer to cv-qualifier-seq X” between the optional cv-qualifier-seq and the end of the function-definition, member-declarator, or declarator. It shall not appear before the optional cv-qualifier-seq
and it shall not appear within the declaration of a static member function (although its type and value category are defined within a static member function as they are within a non-static member function).
So you can't use this-> here, since it is before the cv-qualifier-seq part of the function declaration.
Wait, no it isn't! Section 8.4.1 says
The declarator in a function-definition shall have the form
D1 ( parameter-declaration-clause) cv-qualifier-seq opt ref-qualifier opt exception-specification opt attribute-specifier-seq opt trailing-return-type opt