Class member visibility in member function declaration signature

核能气质少年 提交于 2020-01-13 18:30:28

问题


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.


回答1:


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.




回答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
    }



回答3:


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



来源:https://stackoverflow.com/questions/13993798/class-member-visibility-in-member-function-declaration-signature

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!