模板对象的模板友元函数和命名空间

Mat*_*t G 8 c++ templates friend argument-dependent-lookup

在下面的C++示例代码中,GCC 6和Clang 3.8不同意正确的行为:

这个人为的例子"有效" - 就像GCC 中的test()函数返回一样o.p.在clang中,它调用(undefined)函数get<int, int, float, double>:

template<typename ...Args>
class obj {
 bool p = false;

 template<typename T, typename... Args2>
 friend T get(const obj<Args2...> &o) { return o.p; }
};

template<typename T, typename... Args>
T get(const obj<Args...> &o);


bool test(const obj<int, float, double> &a) {
 return get<int>(a);
}
Run Code Online (Sandbox Code Playgroud)

将相同的代码放在命名空间中会导致GCC执行与clang相同的操作.

namespace ns {

template<typename ...Args>
class obj {
 bool p = false;

 template<typename T, typename... Args2>
 friend T get(const obj<Args2...> &o) { return o.p; }
};

template<typename T, typename... Args>
T get(const obj<Args...> &o);

}

bool test(const ns::obj<int, float, double> &a) {
 return ns::get<int>(a);
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/g/sWrXQOhttps://godbolt.org/g/9tIXwe

哪个编译器是"正确的",并且通常有一种方法可以内联定义友元成员模板函数,而不必声明它然后单独定义它.就是这样的:

struct Foo {
 friend bool bar() { return true; } // declares *and* defines a free function bar
 template<typename T> T bar2() { return true; }  // doesn't work!
};
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 5

关于friend类模板中定义的函数模板有两个未解决的问题:15452174.前者质疑它的有效程度,后者是基于这些功能模板的实际实例可能出现的odr违规.我不确定哪个编译器是正确的(之前认为两者都是错误的),但是在标准中可能只是在这种情况下正确行为的指标不正确或不正确.

理想情况下,代码应该编译(待解决问题):

template<typename ...Args>
class obj {
 bool p = false;

 template<typename T, typename... Args2>
 friend T get(const obj<Args2...> &o) { return o.p; }
};

template<typename T, typename... Args>
T get(const obj<Args...> &o);
Run Code Online (Sandbox Code Playgroud)

friend声明首先声明get,所以这造成最内层的命名空间的新成员:::get.外部声明只是重新声明了相同的功能,所以真的只有一个::get.来自[temp.over.link]:

如果包含表达式的两个函数定义满足单定义规则(3.2),则涉及模板参数的两个表达式被认为是等效的,除了用于命名模板参数的标记可能不同,只要用于命名模板参数的标记在一个表达式被另一个标记替换,该标记在另一个表达式中命名相同的模板参数.为了确定两个相关名称(14.6.2)是否等效,只考虑名称本身,而不是模板上下文中的名称查找结果.

使用不同的模板参数名称(Args...vs Args2...)很好 - 函数模板的第二个声明::get是有效的,并允许查找以找到它.

这让我们:

bool test(const obj<int, float, double> &a) {
#ifdef UNQUAL
    return get<int>(a);     // unqualified
#else
    return ::get<int>(a);   // qualified
#endif
}
Run Code Online (Sandbox Code Playgroud)

这两个都应该工作 - 因为我们重新声明函数在命名空间范围内,我们甚至不必担心ADL.两个调用都应该找到::get<int>(obj<int, float, double> ),这是一个friend,所以代码应该编译和链接.gcc允许不合格的呼叫而不是合格的呼叫,clang不允许.

我们可以通过简单地不在类中定义函数模板来完全回避CWG问题:

template<typename ...Args>
class obj {
 bool p = false;

 template<typename T, typename... Args2>
 friend T get(const obj<Args2...> &o);
};

template<typename T, typename... Args>
T get(const obj<Args...> &o) { return o.p; }
Run Code Online (Sandbox Code Playgroud)

有了这个配方,既编译允许双方的合格和不合格的调用get.


如果我们重写代码,这obj是一个类而不是类模板,但所有其他条件相同:

class obj {
    bool p = false;

    template <class T>
    friend T get(const obj& o) { return o.p; }
};

template <class T> T get(const obj& );

bool test(const obj& a) {
#ifdef UNQUAL
    return get<int>(a);
#else
    return ::get<int>(a);
#endif
}
Run Code Online (Sandbox Code Playgroud)

两个编译器都允许这两个调用.但是,obj作为普通类和类模板之间的规则我没有区别.

  • @MattG外部模板要求最好在[本评论]中解释(http://stackoverflow.com/questions/2953684/why-doesnt-adl-find-function-templates#comment29830195_2953783).另外,我并不是100%确定我的推理是关于编译器的.希望别人在某个时候介入这里. (2认同)