如何从常量引用或通过转发模板临时构造对象
How to construct an object either from a const reference or temporary via forwarding template
考虑这个最小的例子
template <class T>
class Foo
{
public:
Foo(const T& t_)
: t(t_)
{
}
Foo(T&& t_)
: t(std::move(t_))
{
}
T t;
};
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
int main()
{
class C
{
};
C c;
makeFoo(c);
}
MSVC 2017 失败,并出现 Foo 的 ctor 的重定义错误。显然,T被推导出为C&而不是预期的C。这究竟是如何发生的,以及如何修改代码以使其执行所包含的操作:从常量引用复制构造 Foo::t 或从 r 值移动构造它。
在C++17中,您可以简单地编写:
template <typename F>
auto makeFoo(F&& f)
{
return Foo(std::forward<F>(f));
}
因为类模板参数推导。
在C++14中,您可以编写:
template <typename F>
auto makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
template <class F, class R = std::decay_t<F>>
Foo<R> makeFoo(F&& f)
{
return Foo<R>(std::forward<F>(f));
}
这是解决问题的干净简单的方法。
衰减是将类型转换为适合存储在某处的类型的正确方法。 它对数组类型做了坏事,但在其他方面做了几乎正确的事情;无论如何,您的代码都不适用于数组类型。
编译器错误是由于引用折叠规则造成的。
X X& X const& X&&
int int& int const& int&&
int& int& int& int&
int const int const& int const& int const&&
int&& int& int& int&&
int const& int const& int const& int const&
这些可能看起来很奇怪。
第一条规则是 const 引用是引用,但对 const 的引用是不同的。 您不能限定"参考"部分;您只能对引用的零件进行持续限定。
当你有T=int&
时,当你计算T const
或const T
时,你只会得到int&
。
第二部分与如何使用 r 和 l 值引用一起工作有关。 当你做int& &&
或int&& &
(你不能直接做;相反,你做T=int&
然后T&&
或T=int&&
和T&
),你总是得到一个左值引用——T&
。 左值胜过右值。
然后我们添加如何推导T&&
类型的规则;如果你传递一个类型为C
的可变左值,你会在调用makeFoo
时得到T=C&
。
所以你有:
template<F = C&>
Foo<C&> makeFoo( C& && f )
作为您的签名,又名
template<F = C&>
Foo<C&> makeFoo( C& f )
现在我们检查Foo<C&>
. 它有两个 ctors:
Foo( C& const& )
Foo( C& && )
对于第一个,引用上的const
被丢弃:
Foo( C& & )
Foo( C& && )
接下来,对引用的引用是引用,左值引用胜过右值引用:
Foo( C& )
Foo( C& )
然后我们开始,两个相同的签名构造函数。
TL;DR - 做这个答案开头的事情。
问题是提供给类的 typename 在一种情况下是引用的:
template <typename F>
Foo<F> makeFoo(F&& f)
{
return Foo<F>(std::forward<F>(f));
}
成为
template <>
Foo<C&> makeFoo(C& f)
{
return Foo<C&>(std::forward<C&>(f));
}
你可能想要一些衰败:
template <typename F>
Foo<std::decay_t<F>> makeFoo(F&& f)
{
return Foo<std::decay_t<F>>(std::forward<F>(f));
}
发生这种情况是因为引用折叠。
代码中的F&&
是转发引用,这意味着它可以是左值引用或右值引用,具体取决于它绑定到的参数的类型。
在您的情况下,如果F&&
绑定到类型C&&
的参数(对C
的右值引用),则F
简单地推导为C
。但是,如果F&&
绑定到类型C&
的参数(如示例中所示),则引用折叠规则将确定为F
推导的类型:
T& & -> T&
T& && -> T&
T&& & -> T&
T&& && -> T&&
因此,F
被推导出为C&
,因为C& &&
坍缩为C&
。
您可以使用 remove_reference 从推导类型中删除任何引用:
remove_reference_t<C> -> C
remove_reference_t<C&> -> C
您可能还希望使用remove_cv
来删除任何潜在的const
(或volatile
)限定符:
remove_cv_t<remove_reference_t<C>> -> C
remove_cv_t<remove_reference_t<C&>> -> C
remove_cv_t<remove_reference_t<C const>> -> C
remove_cv_t<remove_reference_t<C const&>> -> C
在C++20中,有一个组合的remove_cvref
特征可以节省一些打字。但是,许多实现只使用decay
,它执行相同的操作,但也将数组和函数类型转换为指针,根据您的用例,这可能是可取的,也可能不是可取的(标准库的某些部分已在 C++20 中从使用decay
切换到使用remove_cvref
)。
- 什么时候调用组成单元对象的析构函数
- 对RValue对象调用的LValue ref限定成员函数
- CMake-按正确顺序将项目与C运行时对象文件链接
- 空基优化子对象的地址
- 将对象数组的引用传递给函数
- 你能重载对象变量名本身返回的内容吗
- C++使用整数的压缩数组初始化对象
- 完美的对象转发阵列
- 自定义类型转换运算符在转发引用上调用时不起作用(当对象按值传递时有效)
- 将可变参数函数参数转发到 std::function 对象
- 如何构造一个 std::variant 类型对象,其自身 Templated 和构造函数转发参数
- 如何从常量引用或通过转发模板临时构造对象
- 转发通用可可对象的返回值
- 可调用对象的完美转发
- 使用std ::函数或转发引用作为高阶函数的通用对象输入参数
- 将构造函数转发到成员对象
- 哪种设计更好:提供对所属对象的直接访问,或者为所属对象提供所属对象转发方法
- 完美转发对象的成员
- 具有移动操作和重新值转发的对象生存期
- C++模板类类型作为参数转发到对象的模板函数