How are templated static member functions parsed?

2019-06-22 16:32发布

问题:

I have never gotten a great explanation of how template argument deduction really works, so I'm not sure how to explain behavior I'm seeing in the following:

template<typename T>
struct Base
{
protected:
    template<bool aBool = true>
    static void Bar(int)
    {
    }
};

template<typename T>
class Derived : public Base<T>
{
public: 
    void Foo() { Base<T>::Bar<false>(5); } 
};

int main()
{
    Derived<int> v;
    v.Foo();
    return 0;
}

This code won't build, and gives the error:

main.cpp: In instantiation of 'void Derived<T>::Foo() [with T = int]':
main.cpp:25:8:   required from here main.cpp:19:15: error: invalid
operands of types '<unresolved overloaded function type>' and 'bool'
to binary 'operator<'

If you change the 2 Base<T>s in Derived to Base<int>, it compiles. If you change the call to Bar() to Base<T>::template Bar<false>(5);, it also compiles.

The one-liner I saw as an explanation for this is that the compiler doesn't know that Bar is a template, presumably because it doesn't know what Base is until a specialization of Derived is declared. But once the compiler starts generating code for Foo(), Base<T> has already been defined, and the type of Bar can be determined. What is causing the compiler to assume the symbol Bar is not a template, and attempting to apply operator<() instead?

I assume it has to do with the rules of when templates are evaluated in the compilation process - I guess what I'm looking for is a good comprehensive explanation of this process, such that the next time I run into code like the below, I can deduce the answer without the help of the good people on stack overflow.

Note I'm compiling with g++ 4.7, with c++x11 support.

回答1:

void Foo() { Base<T>::Bar<false>(5); } 

In this context Base<T> is a dependent name. To access a member template of a dependent name you need to add the template keyword:

void Foo() { Base<T>::template Bar<false>(5); } 

Otherwise Base<T>::Bar will be parsed as a non-template member and < as less-than.

As of why the template is required, the reason is two-phase lookup. The error is triggered during the first pass, before the type is substituted, so the compiler does not know what is the definition of Base<T>. Consider for example that you added an specialization of Bar for int that had a non-template Bar member (say for example an int member). Before substituting T into Foo, the compiler does not know if there is an specialization for the type.