考虑这段代码:
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 年的问题。甚至问题1430和1554也可以被视为处理类似的问题。
特别是,问题 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++ 标准 - 讨论组中提出这个问题;这应该引起正确的人的注意。