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

All*_*lan 8 c++ templates namespaces metaprogramming c++11

假设以下代码:

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

由于某些原因我无法理解,它会产生以下编译错误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]
 }
 ^
Run Code Online (Sandbox Code Playgroud)

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

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

Link(Link(10))
10
Run Code Online (Sandbox Code Playgroud)

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

dyp*_*dyp 7

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

(1)在声明中

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
Run Code Online (Sandbox Code Playgroud)

根据[basic.scope.pdecl]/1 ,只有在完整的声明之后才能看到名称RemoveLinks,或者更确切地说,这个重载.根据[dcl.decl]/4 ,trailing-return-type是声明符的一部分.另见这个答案.RemoveLinks

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

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

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

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

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

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

  • "我说我们从轨道起飞整个站点.这是唯一确定的方法." (2认同)

Shm*_*ine 2

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

如果我修改模板专业化的签名,我可以毫无问题地成功编译:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val))
Run Code Online (Sandbox Code Playgroud)

并将返回类型设置为 const。这可能会导致编译器警告,因为 const 毫无意义,但可以忽略。我测试了它,它对我来说对 gcc 工作得很好,但对 icpc 或 clang 不起作用:

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(RemoveLinks(link.val)) const
Run Code Online (Sandbox Code Playgroud)

我发现来自 Intel 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;
};
Run Code Online (Sandbox Code Playgroud)

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

template<typename T>
constexpr auto RemoveLinks(const Link<T>& link) -> decltype(links.value_type)
Run Code Online (Sandbox Code Playgroud)

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

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

希望这可以帮助。