9

假设以下代码:

#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) 会导致这样的错误?

4

2 回答 2

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,尾随返回类型是声明符的一部分。另请参阅此答案

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

如果我们现在查看依赖名称是如何解析的,我们会发现 [temp.dep.res]:

在解析从属名称时,会考虑来自以下来源的名称:

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

RemoveLinks由于声明点 (1),第一个项目符号没有找到第二个重载。第二个没有找到重载,因为命名空间Util没有与任何参数相关联。这就是为什么将所有内容放在全局名称空间或名称空间中Util按预期工作的原因(现场示例)。

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

于 2013-08-29T16:25:23.990 回答
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):错误:模板实例化导致“ auto (const Link<Link<int &>> &)->const int &”的意外函数类型(名称的含义可能在模板声明后发生变化——模板的类型为“ auto (const Link<T> &)->decltype((<expression>))”) std::cout << Utils::RemoveLinks(link) << std::endl; ^ 在“Utils”的实例化过程中检测到::RemoveLinks" 基于<Link<int &>>第 48 行的模板参数

不幸的是,上述答案更多的是 gcc 的解决方法,而不是您问题的答案。如果我有更多/更好的补充,我会更新这个。

编辑

decltype(RemoveLinks(link.val)) 似乎实际上遵循递归,因此它返回 int& 而不是 Link。

编辑#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 个编译器上。

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

希望这可以帮助。

于 2013-08-29T13:55:18.003 回答