如何通过转发模板从 const 引用或临时对象构造对象
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 构造函数的重新定义错误。显然 T 被推断为 C& 而不是预期的 C。这究竟是如何发生的以及如何修改代码以使其执行预期的操作:从 const 引用复制构造 Foo::t 或从 r 移动构造它-值。
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 的引用是不同的。您不能限定 "reference" 部分;您只能对引用的部分进行 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&>
。它有两个演员:
Foo( C& const& )
Foo( C& && )
对于第一个,const
引用被丢弃:
Foo( C& & )
Foo( C& && )
接下来,对引用的引用就是引用,左值引用胜过右值引用:
Foo( C& )
Foo( C& )
好了,两个相同的签名构造器。
TL;DR -- 做这个答案开头的事情。
问题是提供给 class 的类型名在一种情况下是参考:
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));
}
在C++17中你可以简单地写:
template <typename F>
auto makeFoo(F&& f)
{
return Foo(std::forward<F>(f));
}
因为class template argument deduction.
在C++14中你可以这样写:
template <typename F>
auto 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
trait which can save some typing. However, many implementations just use decay
,它做同样的事情,但也将数组和函数类型转换为指针,这可能需要也可能不需要,具体取决于您的用例 (在 C++20 中,标准库的某些部分已从使用 decay
切换到使用 remove_cvref
。
考虑这个最小的例子
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 构造函数的重新定义错误。显然 T 被推断为 C& 而不是预期的 C。这究竟是如何发生的以及如何修改代码以使其执行预期的操作:从 const 引用复制构造 Foo::t 或从 r 移动构造它-值。
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 的引用是不同的。您不能限定 "reference" 部分;您只能对引用的部分进行 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&>
。它有两个演员:
Foo( C& const& )
Foo( C& && )
对于第一个,const
引用被丢弃:
Foo( C& & )
Foo( C& && )
接下来,对引用的引用就是引用,左值引用胜过右值引用:
Foo( C& )
Foo( C& )
好了,两个相同的签名构造器。
TL;DR -- 做这个答案开头的事情。
问题是提供给 class 的类型名在一种情况下是参考:
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));
}
在C++17中你可以简单地写:
template <typename F>
auto makeFoo(F&& f)
{
return Foo(std::forward<F>(f));
}
因为class template argument deduction.
在C++14中你可以这样写:
template <typename F>
auto 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
trait which can save some typing. However, many implementations just use decay
,它做同样的事情,但也将数组和函数类型转换为指针,这可能需要也可能不需要,具体取决于您的用例 (在 C++20 中,标准库的某些部分已从使用 decay
切换到使用 remove_cvref
。