16

考虑下面的代码片段:

template <class T>
using identity = T;

template <class T>
void foo(identity<T>&&) {}

int main()
{
  int i{};
  foo(i);
}

i是一个左值,因此如果foo声明一个转发引用参数,它应该编译。但是,如果identity<T>&&变成 be int&&,它应该会引发错误。

该代码在 GCC 6.0.0 ( demo )中编译。

该代码无法在 Clang 3.7.0 ( demo ) 中编译并显示错误消息:

error: no known conversion from 'int' 
to 'identity<int> &&' (aka 'int &&') for 1st argument

哪一个是对的?

4

2 回答 2

8

考虑这段代码:

template<class T> using identity = T;

template<class T> void foo(identity<T>&&) { } //#1

template<class T> void foo(T&&) { } //#2

int main()
{
  int i{};
  foo(i);
}

GCC 和 Clang 都拒绝它,因为它#2#1. 如果它们实际上是同一个模板,我们可以期望#1其行为方式与 完全相同#2,这意味着它identity<T>&&应该充当转发引用。按照这个逻辑,我们不知道哪个是对的,但GCC至少是一致的。

这也与 [14.5.7p2] 标准中的一个非常相似的示例一致。

我们还应该考虑模板参数推导在这种情况下的工作方式。如果identity是类模板,它的形式可以与函数参数的类型相匹配,而无需查看其定义,从而允许编译器推导出T. 但是,这里我们有一个别名模板;除非被替换,否则T不能推导出int或或其他任何东西。否则,我们匹配的是什么?替换完成后,函数参数成为转发引用。int&identity<T>T

以上所有内容都支持将identity<T>&&(and identity<T&&>) 视为等同于转发引用的想法。

但是,似乎还有更多的方法是立即将别名模板 ID 替换为相应的类型 ID。第 [14.5.7p3] 段说:

但是,如果模板 ID 是依赖的,则后续模板参数替换仍适用于模板 ID。[ 例子:

template<typename...> using void_t = void; 
template<typename T> void_t<typename T::foo> f(); 
f<int>(); // error, int does not have a nested type foo 

—结束示例]

这似乎与您的示例没有太大关系,但它实际上表明在某些情况下仍会考虑模板 ID 的初始形式,而与替换的类型 ID 无关。我想这打开了identity<T>&&实际上不能被视为转发参考的可能性。

这个区域似乎在标准中没有被详细说明。这显示在处理类似问题的未解决问题的数量上,在我看来,它们都属于同一类别:在什么情况下,在实例化时应该考虑模板 ID 的初始形式,即使它应该被替换为遇到时立即对应的 type-id。请参阅1980 年2021 年2025 年的问题。甚至问题14301554也可以被视为处理类似的问题。

特别是,问题 1980包含以下示例:

template<typename T, typename U> using X = T;
template<typename T> X<void, typename T::type> f();
template<typename T> X<void, typename T::other> f();

附注:

CWG 认为这两个声明不应等同。

(CWG——核心工作组)

类似的推理方式可能适用于您的示例,identity<T>&&不等同于转发参考。这甚至可能具有实用价值,当您想要的只是对推导的 T 的右值引用时,它可以作为一种避免转发引用贪婪的直接方法。

所以,我认为你提出了一个非常有趣的问题。您的示例可能值得作为issue 1980的注释添加,以确保在起草决议时考虑到这一点。

在我看来,就目前而言,您的问题的答案是响亮的“谁知道?”。


更新:在对另一个相关问题的评论中,Piotr S.指出了问题 1700,该问题已被关闭为“不是缺陷”。它指的是该问题中描述的非常相似的案例,并包含以下基本原理:

因为函数参数的类型是一样的,不管是直接写还是通过别名模板写的,两种情况下的推导处理方式都一样。

我认为它同样适用于这里讨论的案例,并且现在解决了这个问题:所有这些形式都应该被视为等同于转发引用。

(看看其他未解决问题的决议是否间接改变了这一点将会很有趣,但它们主要处理替换失败而不是本身的扣除,所以我想这种间接影响不太可能。)


所有标准参考均参考当前工作草案 N4431,即最终 C++14 之后的第二个草案。

请注意,[14.5.7p3] 中的引用是最近添加的,包含在最终 C++14 版本之后,作为DR1558的分辨率。我认为我们可以期待在这方面的进一步补充,因为其他问题以一种或另一种方式得到解决。

在此之前,可能值得在ISO C++ 标准 - 讨论组中提出这个问题;这应该引起正确的人的注意。

于 2015-04-24T21:07:15.263 回答
7

它不是转发参考。C++14 (n4140) 14.8.2.1/3(强调我的):

...如果P对 cv 非限定模板参数的右值引用并且参数是左值,则使用类型“左值引用A”代替A类型推导。

这是标准的一部分,它指定了转发引用的工作方式。P,函数参数的类型,是“rvalue reference to identity<T>”类型。identity<T>是模板参数的类型,但它本身不是模板参数,所以转发引用扣除规则不适用。

我们还可以看看 14.5.7/2 对别名模板的看法:

template-id指代别名模板的特化时,它等价于通过将其模板参数 替换为别名模板的type-id中的模板参数而获得的关联类型

所以被替换的别名等价于 的类型T但 14.8.2.1/3 读作“对 ... 模板参数的引用”,而不是“对 ... 模板参数的类型的引用”。

于 2015-04-24T15:16:42.533 回答