Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clang does not find function instantiated after function definition in template context

Tags:

c++

clang

I have been experimenting with code derived from the "C++ Seasoning" presentation by Sean Parent, and have boiled my problem down to the following code:

#include <memory>

struct container {
    struct concept {
        virtual ~concept() {}
        virtual void foo_() = 0;
    };

    template <class T> struct model : concept {
        model (T x) : data_(x) {}

        void foo_() {
            foo(data_); // Line 13
        }

        T data_;
    };

    template <class T>
    container(T x) : self_(new model<T>(x)) {} // Line 20

    std::unique_ptr<concept> self_;

    friend void foo(container &c) { c.self_->foo_(); }
};

void foo(int i) // Line 27
{
}

int main()
{
    int i = 5;
    container c(i); // Line 34
    foo(c);
}

The problem I have is that this code that compiles with g++, and yet not with Clang.

Clang gives me the following error messages:

prio.cpp:13:13: error: call to function 'foo' that is neither visible in the
      template definition nor found by argument-dependent lookup
            foo(data_);
            ^
prio.cpp:20:32: note: in instantiation of member function
      'container::model<int>::foo_' requested here
    container(T x) : self_(new model<T>(x)) {}
                               ^
prio.cpp:34:15: note: in instantiation of function template specialization
      'container::container<int>' requested here
    container c(i);
              ^
prio.cpp:27:6: note: 'foo' should be declared prior to the call site
void foo(int i)
     ^

My understanding is that overload resolution during templates occurs at the point of instantiation. In this case, that is line 34 (as marked above.) At this point, the global "foo" function is known. And yet, it appears not to resolve.

Note for posterity: This was with Clang built from trunk on 14/Jan/14

Is this a bug in Clang then, or with g++?

like image 863
Kaz Dragon Avatar asked Nov 01 '22 07:11

Kaz Dragon


1 Answers

Gcc is wrong in this case, the code should not compile; but this is completely unrelated to the template. Friend declarations are particular in that they provide a declaration for a namespace level entity, but the declaration is not visible for normal lookup until a namespace declaration is also seen by the compiler.

Consider the simplified example:

struct X {
   friend void f(int);   // [1]
   void g() { f(1); }    // [2]
};
void h() { f(1); }       // [3]
void f(int);             // [4]
void i() { f(1); }       // [5]

The friend declaration [1] inside the X class provides a declaration for a namespace level function f taking an int, but that declaration is not visible at namespace level until a namespace level declaration is present in [4]. Both [2] and [3] will fail to compile, although [5] will compile since at that point the compiler will have parsed the function declaration.

So how can the declaration in [1] be used by the compiler to resolve a call? In this particular case never. The friend declaration can only be found by argument dependent lookup, but ADL will only look inside X if one of the arguments to the function call is of type X. In this case, the function does not have any argument X, so lookup will never use the friend declaration for anything other than lifting the restrictions of access to the variables of X.

That is:

struct Y {
   friend void f(int) {}
};

Without a latter namespace level declaration for f will declare and define a function that cannot be used anywhere in your program (lookup won't ever find it).

The simple workaround for your problem is to provide a declaration for the function at namespace level before the definition of the class:

#include <memory>

void foo(int);
struct container { // ...
like image 132
David Rodríguez - dribeas Avatar answered Nov 13 '22 04:11

David Rodríguez - dribeas