3

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.

Xeo
  • 129,499
  • 52
  • 291
  • 397
yuri kilochek
  • 12,709
  • 2
  • 32
  • 59

3 Answers3

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

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • "If we had accessed B::a, then the a would be a member of an unknown specialization"... Surely you mean if we had accessed `S::a` ? – Ben Voigt Dec 22 '12 at 17:46
  • @BenVoigt if `B` will be a class and `a` a static data member, then it could work when instantiated :) I meant to talk about some random other scenario he could be having. Of course, `S::a` would also work and could be valid depending on what `B` is when instantiating. – Johannes Schaub - litb Dec 22 '12 at 17:49
2

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
    }
Bo Persson
  • 90,663
  • 31
  • 146
  • 203
  • 1
    Okay, then how does adding `this->` work? Does it postpone name resolution until after the class body is parsed? – yuri kilochek Dec 21 '12 at 17:35
  • @BenVoigt: If the `this->` thing works, don't things like http://ideone.com/AThdkE allow infinite self-references? – aschepler Dec 21 '12 at 18:47
  • @Ben `this->f` is not a dependent name in his case because he has no dependent base classes (and although `this` is type-dependent, that does not make `this->f` dependent in C++11). – Johannes Schaub - litb Dec 21 '12 at 18:50
  • @Johannes: That's what I thought aswell, but the quote Ben's answer suggests otherwise. – Xeo Dec 21 '12 at 18:52
  • @Xeo: Well, there's "Is `a` found in `this->a`?" and "Can I write `declspec(this->a(f))` there?" And the answers appear to be Yes and No respectively. – Ben Voigt Dec 21 '12 at 18:55
  • @aschepler: I think g++ (ideone version) is rejecting that for the wrong reason. See the bolded quote in my answer. – Ben Voigt Dec 21 '12 at 19:24
2

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

Community
  • 1
  • 1
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Now, if you'd add a quote / explanation on why the second snippet doesn't work as-is... :) (`§9.2/2`) – Xeo Dec 21 '12 at 18:56
  • 1
  • 1
    Your last "However" addition is incorrect aswell, because a non-static member function is not a non-static data member. – Johannes Schaub - litb Dec 21 '12 at 19:00
  • @Johannes: Thanks, fixed the however. The important rule was in the previous paragraph. – Ben Voigt Dec 21 '12 at 19:04
  • @Johannes: I think you're wrong about the other point though. The rule says "Such names are unbound and are looked up at the point of the template instantiation in both the context of the template definition and the context of the point of instantiation." That means for example that `*this << t;` would search for `operator<<` in the namespace where the class is as well as the namespace where the instantiation is. But it will NOT be looked up during first phase. – Ben Voigt Dec 21 '12 at 19:05
  • How about http://ideone.com/sJJFAC ? Avoids the `this` issue, but I still think there must be some rule that future class member declarations can't be visible when determining the type (not initializer/definition) of a member declaration. – aschepler Dec 21 '12 at 19:09
  • @aschepler: Ditto http://ideone.com/aMSQmg Which carries a much more explanatory diagnostic (it is a dependent name, the lookup occurs for `X` specifically, but it's still an incomplete type there). – Ben Voigt Dec 21 '12 at 19:11
  • Ah, I didn't notice that `A a;` uses the template parameter `A`. I thought it is like `std::string a;`. But that doesn't change that this program is invalid because of 14.6.2.1p6: *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.*. – Johannes Schaub - litb Dec 21 '12 at 19:18
  • @BenVoigt in `*this << t`, we have "If an operand of an operator is a type-dependent expression, the operator also denotes a dependent name." (`t`). However for our `this->a`, we have no such rule that makes `this->a` dependent. Note that this is entirely logical, because the compiler when parsing the member knows that if it would instantiate the code, it would look into the surrounding scope, and the surrounding scope would not be affected by instantiation semantics (because there are no dependent base classes). So the rules in C++11 actually reflects the desired semantics quite well. – Johannes Schaub - litb Dec 21 '12 at 19:19
  • @JohannesSchaub-litb: But it does refer to a member of the current instantiation, so I don't see that rule being a problem. – Ben Voigt Dec 21 '12 at 19:24
  • @BenVoigt no it does not refer to a member of the current instantiation because in trailing return types, the surrounding class type is still incomplete (see the note at the end of 5.1.1p3). Hence the name does not "refer to at least one member of the current instantiation or a non-dependent base class thereof." – Johannes Schaub - litb Dec 21 '12 at 19:26
  • @Johannes: I think I see what you're getting at about `this->a` not being a dependent name. With dependent base classes, it would be "a member of an unknown specialization" (14.6.2.1p5). And that makes it a dependent name (14.6.2.1p8). It is instead "a member of the current specialization", which is not a dependent name. – Ben Voigt Dec 21 '12 at 19:29
  • @BenVoigt yes with dependent base classes it is a member of an unknown specialization and that would make it dependent by 14.6.2.2p5. As it is now, it is neither a member of the current, nor a member of an unknown specialization. And hence, 14.6.2.1p6 applies and the template/program is ill-formed, no diagnostic required. If you put the declaration `A a;` before the member function template, then `this->a` is a member of the current instantiation, and 14.6.2.2p5 would make it dependent because it is "a member of the current instantiation and the type of the referenced member is dependent". – Johannes Schaub - litb Dec 21 '12 at 19:32
  • @JohannesSchaub-litb: Oh, it is a member of the current specialization in the first code sample. In the second code sample, phase-one lookup fails, so it is then not a member of the current specialization. That right? – Ben Voigt Dec 21 '12 at 19:35
  • I posted an explanation in my answer of this. – Johannes Schaub - litb Dec 22 '12 at 17:42