Clang在模板上下文中的函数定义之后找不到实例化的函数

Kaz*_*gon 5 c++ clang

我一直在试验Sean Parent的"C++ Seasoning"演示文稿中的代码,并将我的问题归结为以下代码:

#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);
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是这个代码用g ++编译,而不是用Clang编译.

Clang给了我以下错误消息:

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)
     ^
Run Code Online (Sandbox Code Playgroud)

我的理解是模板期间的重载解析发生在实例化时.在这种情况下,即第34行(如上所示).此时,全局"foo"函数是已知的.然而,似乎没有解决.

后人的注意事项:这是Clang于14/Jan/14建造的

这是Clang中的一个错误,还是使用g ++?

Dav*_*eas 2

在这种情况下,Gcc 是错误的,代码应该编译;但这与模板完全无关。友元声明的特殊之处在于,它们提供命名空间级别实体的声明,但该声明对于正常查找是不可见的,直到编译器也看到命名空间声明为止。

考虑一个简化的例子:

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]
Run Code Online (Sandbox Code Playgroud)

类内的友元声明 [1]为采用 的X命名空间级别函数提供了声明,该声明在命名空间级别不可见,直到 [4] 中出现命名空间级别声明。[2] 和 [3] 都将无法编译,尽管 [5] 将编译,因为此时编译器将解析函数声明。fint

那么编译器如何使用[1]中的声明来解析调用呢?在这种特殊情况下永远不会。友元声明只能通过参数相关查找找到,但 ADL 仅X在函数调用的参数之一为 类型时才会查找内部X。在这种情况下,该函数没有任何参数X,因此除了解除对 的变量的访问限制之外, lookup绝不会将该声明用于任何其他用途。friendX

那是:

struct Y {
   friend void f(int) {}
};
Run Code Online (Sandbox Code Playgroud)

如果没有后面的命名空间级别声明,则将f声明并定义一个不能在程序中任何地方使用的函数(查找将永远找不到它)。

解决您的问题的简单方法是在类定义之前在命名空间级别提供函数声明:

#include <memory>

void foo(int);
struct container { // ...
Run Code Online (Sandbox Code Playgroud)