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.