身份别名模板是否可以作为转发引用

Can an identity alias template be a forwarding reference?

本文关键字:转发 引用 别名 是否 身份      更新时间:2023-10-16

请考虑以下代码片段:

template <class T>
using identity = T;
template <class T>
void foo(identity<T>&&) {}
int main()
{
int i{};
foo(i);
}

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

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

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

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

哪一个是对的?

请考虑以下代码:

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不能推导出为intint&或其他任何东西,除非identity<T>T取代。否则,我们匹配的是什么?替换完成后,函数参数将成为转发引用。

以上所有内容都支持将identity<T>&&(和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 的初始形式,即使它应该在遇到时立即被相应的类型 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 的右值引用。

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

在我看来,你的问题的答案是,就目前而言,一个响亮的"谁知道?


更新:在对另一个相关问题的评论中,Piotr S.指出了问题1700,该问题被关闭为"不是缺陷"。它提到该问题中描述的非常类似的情况,并包含以下理由:

由于函数参数的类型是相同的,无论是直接编写还是通过别名模板编写,因此在这两种情况下,必须以相同的方式处理扣除。

我认为它同样适用于这里讨论的情况,并暂时解决了这个问题:所有这些表格都应被视为等同于转发参考。

(看看这是否被其他未决问题的决议间接改变会很有趣,但它们主要处理替代失败而不是本身的演绎,所以我想这种间接影响是不太可能的。


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

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

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

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

。如果P是对cv 不合格的右值引用 模板参数,参数是左值,类型"左值引用A"用于 类型扣除的A地点。

这是指定转发引用如何工作的标准的一部分。P,函数参数的类型,类型为"对identity<T>的右值引用"。identity<T>是模板参数的类型,但本身不是模板参数,因此转发引用扣除规则不适用。

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

模板id 引用别名模板的专用化时,它等效于通过将其模板参数替换为别名的类型id中的模板参数而获得的关联类型模板。

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