命名空间中的模板函数会导致错

时间:2013-08-28 16:51:25

标签: c++ templates c++11 namespaces metaprogramming

假设以下代码:

#include <iostream>

template<typename T>
struct Link
{
    Link(T&& val) : val(std::forward<T>(val)) {}

    T val;
};

template<typename T>
std::ostream& operator<<(std::ostream& out, const Link<T>& link)
{
    out << "Link(" << link.val << ")";
    return out;
}

template<typename T>
auto MakeLink(T&& val) -> Link<T>
{
    return {std::forward<T>(val)};
}

namespace Utils {
template<typename Any>
constexpr auto RemoveLinks(const Any& any) -> const Any&
{
    return any;
}

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
{
    return RemoveLinks(link.val);
}

} /* Utils */

int main()
{
    int k = 10;

    auto link = MakeLink(MakeLink(k));

    std::cout << link << std::endl;
    std::cout << Utils::RemoveLinks(link) << std::endl;
}

由于某些原因我无法理解,它会使用g++-4.8生成以下编译错误:

/home/allan/Codes/expr.cpp: In instantiation of ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:88:32:   required from ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = Link<int&>; decltype (Utils::RemoveLinks(link.val)) = const int&]’
/home/allan/Codes/expr.cpp:100:41:   required from here
/home/allan/Codes/expr.cpp:88:32: error: invalid initialization of reference of type ‘const Link<int&>&’ from expression of type ‘const int’
     return RemoveLinks(link.val);
                                ^
/home/allan/Codes/expr.cpp:89:1: error: body of constexpr function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const Link<int&>&]’ not a return-statement
 }
 ^
/home/allan/Codes/expr.cpp: In function ‘constexpr decltype (Utils::RemoveLinks(link.val)) Utils::RemoveLinks(const Link<T>&) [with T = int&; decltype (Utils::RemoveLinks(link.val)) = const int&]’:
/home/allan/Codes/expr.cpp:89:1: warning: control reaches end of non-void function [-Wreturn-type]
 }
 ^

而clang 3.3给出:

test.cc:34:12: error: reference to type 'const Link<int &>' could not bind to an lvalue of type 'const int'
return RemoveLinks(link.val);
       ^~~~~~~~~~~~~~~~~~~~~
test.cc:46:25: note: in instantiation of function template specialization 'Utils::RemoveLinks<Link<int &> >' requested here
std::cout << Utils::RemoveLinks(link) << std::endl;

但是,如果删除了命名空间Utils,那么它编译时没有错误(包括gcc和clang)和执行输出:

Link(Link(10))
10

为什么在命名空间中定义这些模板函数(RemoveLinks)会导致此类错误?

2 个答案:

答案 0 :(得分:7)

此问题是声明(1)与从属名称查找(2)相结合的问题的结果。

(1)在声明中

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))

根据[basic.scope.pdecl] / 1,名称RemoveLinks,或者更确切地说,RemoveLinks的重载仅在完整声明符之后可见。根据[dcl.decl] / 4, trailing-return-type 是声明符的一部分。另请参阅this answer

(2)在表达式RemoveLinks(link.val)中,名称RemoveLinks依赖于[temp.dep] / 1,因为link.val是依赖的。

如果我们现在查找如何解析依赖名称,我们找到[temp.dep.res]:

  

在解析依赖名称时,会考虑以下来源的名称:

     
      
  • 在模板定义时可见的声明。
  •   
  • 来自实例化上下文和定义上下文中与函数参数类型相关联的名称空间的声明。
  •   

由于声明(1),第一颗子弹找不到RemoveLinks的第二次重载。第二个没有找到重载,因为命名空间Util与任何参数都没有关联。这就是为什么将全局命名空间或命名空间Util中的所有内容都按预期工作(Live example)。

出于同样的原因,在 trailing-return-type 中使用 qualified-id (如-> decltype(Util::RemoveLinks(link.val))在这里没有帮助。

答案 1 :(得分:2)

我尝试使用GCC 4.8.1编译上面的示例代码,使用clang和Intel icpc并获得与您相同的错误消息。

如果我修改模板特化的签名,我能够顺利编译成功编译:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))

并将返回类型设为const。这可能会导致编译器警告,因为const没有意义,但可以忽略。我测试了它,它对我来说对gcc工作正常,但不是icpc或clang:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val)) const

我发现英特尔icpc的错误消息(带有原始代码)是最有用的信息:

code.cc(48):错误:模板实例化导致了意外的函数类型&#34; auto (const Link<Link<int &>> &)->const int &&#34; (自模板声明以来,名称的含义可能已更改 - 模板的类型为&#34; auto (const Link<T> &)->decltype((<expression>))&#34;) std::cout << Utils::RemoveLinks(link) << std::endl;                                                 ^           在实例化过程中检测到&#34; Utils :: RemoveLinks&#34;基于第48行的模板参数<Link<int &>>

不幸的是,上面的答案更多的是gcc的解决方法,而不是你的问题的答案。如果我还有更多/更好的内容,我会更新此内容。

修改

似乎decltype(RemoveLinks(link.val))实际上是在递归之后,它返回int&amp;而不是链接。

编辑#2

已经报道了LLVM中有关由decltype递归问题引起的崩溃的错误。看起来这绝对是一种错误,但似乎存在于C ++的多个实现中。

如果在链接结构中为类型T创建别名并且decltype引用别名而不是返回类型,则可以很容易地解决问题。这将消除递归。如下:

template<typename T>
struct Link
{
    Link(T&& val) : val(std::forward<T>(val)) {}
    using value_type = T;
    T val;
};

然后相应地更改RemoveLinks签名以引用此别名:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(links.value_type)

此代码成功构建于所有3个编译器上。

我将向编译器提交一些错误报告,看看他们能做些什么。

希望这有帮助。