Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In C++ template function, why does dependent function call give "not declared" error?

Tags:

c++

Inside a C++ template function foo(), a call to ::bar(TT*) gives the following error under gcc 4.4.3:

g++ -o hello.o -c -g hello.cpp
hello.cpp: In function 'void foo(std::vector<TT*, std::allocator<TT*> >&)':
hello.cpp:8: error: '::bar' has not been declared

Here's the offending code:

// hello.cpp

#include <vector>

template<typename TT> void foo(std::vector<TT*> &vec)
{
    TT *tt;
    ::bar(tt);
    vec.push_back(tt);
}

class Blah
{
};

void bar(Blah *&)
{
}

int main(int argc, char *argv[])
{
    std::vector<Blah*> vec;
    foo(vec);

    return 0;
}

C++ distinguishes between symbols that are dependent on the template parameter (TT, here) and those symbols that are independent and can be evaluated immediately.

Clearly, the compiler thinks my ::bar(TT*) call is independent and tries to resolve it immediately. Just as clearly, that function call is dependent on TT because the function call takes a parameter of type TT*, so the compiler should wait until the foo(vec) instantiation to resolve ::bar(TT*).

Is this a gcc bug or am I missing something subtle about C++ templates?

EDIT: here's a slightly more complicated example with two versions of ::bar() to clarify that declaration order is not the issue with my problem. When parsing the template, the compiler has no way of knowing if main() down below is going to instantiate the template function with TT=Blah or with TT=Argh. Therefore the compiler should not be giving an error until line 35 line 28 at the earliest (if ever). But the error is given for line 8 line 16.

EDIT #2: improved this example.

EDIT #3: added corrections to this example to make it work as desired. The bar(tt) now correctly refers to bar(Blah*). Rationale given below. (Thanks everyone).

// hello.cpp
#include <vector>

class XX {};
void bar(XX*) {}

class CC {
public:
    void bar();
    void bar(int *);
    void bar(float *);

    template<typename TT> static void foo(std::vector<TT*> &vec);
};

template<typename TT>
void CC::foo(std::vector<TT*> &vec) {
    using ::bar;
    TT *tt;
    bar(tt);
    vec.push_back(tt);
}

class Argh {};
void bar(Argh *&aa) { aa = new Argh; }

class Blah {};
void bar(Blah *&bb) { bb = new Blah; }

int main(int argc, char *argv[]) {
    std::vector<Blah*> vec;
    CC::foo(vec);
    return 0;
}
like image 894
corecursion Avatar asked Oct 16 '10 23:10

corecursion


People also ask

How do you call a function in a template?

A function template starts with the keyword template followed by template parameter(s) inside <> which is followed by the function definition. In the above code, T is a template argument that accepts different data types ( int , float , etc.), and typename is a keyword.

What is the format in declaring a function template?

The format for declaring function templates with type parameters is: template <class identifier> function_declaration; template <typename identifier> function_declaration; The only difference between both prototypes is the use of either the keyword class or the keyword typename.

Can member functions be declared as template?

The term member template refers to both member function templates and nested class templates. Member function templates are function templates that are members of a class or class template. Member functions can be function templates in several contexts.

How the instantiation of function template happens?

When a function template is first called for each type, the compiler creates an instantiation. Each instantiation is a version of the templated function specialized for the type. This instantiation will be called every time the function is used for the type.


2 Answers

Nobody has yet pointed out any part of the current Standard that says I can't.

C++03 does not make the name ::bar dependent. Dependency happens for type names by dependent types, and for non-type names by dependent expressions that are either type or value dependent. If a name is looked up in a dependent type, it becomes a type-dependent id-expression (14.6.2.2/3 last bullet), and its lookup is delayed until instantiation. The name ::bar is no such dependent expression. If you were to call bar(tt), a special rule of C++03 at 14.2.6 says

In an expression of the form:

postfix-expression ( expression-listopt )

where the postfix-expression is an identifier, the identifier denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.6.2.2).

So you need to remove the :: in order to make it an identifier and to make it dependent by this special rule.

The reason I can't remove the :: is that in my real code, the template function foo is a member function of class CC, and there exist a family of overloaded member functions CC::bar(...), meaning I need to qualify ::bar(TT*) to avoid defaulting to CC::bar(...). That's what :: exists for, I'm surprised if the Standard says I can't use :: here

The proper way to solve it is to introduce a using declaration into the local scope of your function.

namespace dummies { void f(); }
template<typename T>
struct S {
  void f();
  void g() { 
    using dummies::f; // without it, it won't work
    f(T()); // with ::f, it won't work
  }
};

struct A { };
void f(A) { } // <- will find this

int main() {
  S<A> a;
  a.g();
}

ADL will not do anything if ordinary lookup finds a class member function. Therefor, you introduce a using declaration, so ordinary lookup doesn't find a class member function, and ADL can advance the declarations visible when instantiating.

But this seems to disagree with you: Stroustrup TC++PL Sp Ed, Section C.13.8.1, Dependent Names: "Basically, the name of a function called is dependent if it is obviously dependent by looking at its arguments or at its formal parameters"

Stroustrup's book is also written for people who possibly don't know C++ yet. It won't try to cover all rules with 100% accuracy, as is normal for these books. The gory details are left for ISO Standard readers.

Also, the formal parameters of a function have nothing to do with whether a function call is dependent or not. In the IS, only actual arguments define dependency of a function name. This was different in an old draft from 1996, which had the notion of implicit and explicit dependency. Implicitly dependency was defined as

A name implicitly depends on a template-argument if it is a function name used in a function call and the function call would have a dif- ferent resolution or no resolution if a type, template, or enumerator mentioned in the template-argument were missing from the program.

[...]

[Example: some calls that depend on a template-argument type T are:

  1. The function called has a parameter that depends on T according to the type deduction rules (temp.deduct). For example, f(T), f(Array), and f(const T*).

  2. The type of the actual argument depends on T. For example, f(T(1)), f(t), f(g(t)), and f(&t) assuming that t has the type T.

A practical example is also given

This ill-formed template instantiation uses a function that does not depend on a template-argument:

template<class T> class Z {
public:
        void f() const
        {
                g(1); // g() not found in Z's context.
                      // Look again at point of instantiation
        }
};

void g(int);
void h(const Z<Horse>& x)
{
        x.f(); // error: g(int) called by g(1) does not depend
               // on template-argument ``Horse''
}

The call x.f() gives rise to the specialization:

void Z<Horse>::f() { g(1); }

The call g(1) would call g(int), but since that call does not depend on the template-argument Horse and because g(int) was not in scope at the point of the definition of the template, the call x.f() is ill- formed.

On the other hand:

void h(const Z<int>& y)
{
        y.f(); // fine: g(int) called by g(1) depends
               // on template-argument ``int''
}

Here, the call y.f() gives rise to the specialization:

void Z<int>::f() { g(1); }

The call g(1) calls g(int), and since that call depends on the tem- plate-argument int, the call y.f() is acceptable even though g(int) wasn't in scope at the point of the template definition. ]

These things are left to history, and even the last traces from it are disappearing slowly, albeit not actively driven (n3126 for instance gets rid of "explicitly depends" at [temp.names]/p4 as a side-effect of another change, because the distinction between "explicitly depends" and "implicitly depends" has never existed in the IS).

like image 81
Johannes Schaub - litb Avatar answered Oct 05 '22 12:10

Johannes Schaub - litb


From section 14.7.2 of the C++ spec:

In an expression of the form:

    postfix-expression ( expression-listopt )

where the postfix-expression is an unqualified-id but not a template-id , the unqualified-id denotes a dependent name if and only if any of the expressions in the expression-list is a type-dependent expression (14.7.2.2).

since ::b is not an unqualified id, it is not a dependent name. If you remove the ::, it is an unqualified name and so is a dependent name. For a non-dependent name, the lookup occurs at the point of the template declaration, not the instantiation, and at that point there is no global declaration of bar visible, so you get an error.

like image 29
Chris Dodd Avatar answered Oct 05 '22 13:10

Chris Dodd