考虑这个简单的功能
template <typename U>
auto mkVector(U&& x0)
{
return std::vector<std::decay_t<U>>{std::forward<U>(x0)};
}
以及 4 种可能的用例,其中参数是左值或右值,并且类型要么隐式说明,要么从参数中推导:
const string lvalue("hello");
// type inferred from arguments
auto v1 = mkVector(lvalue); // argument is a lvalue
auto v2 = mkVector(string{}); // argument is a rvalue
// type is explicilty stated
auto v3 = mkVector<string>(lvalue); // argument is a lvalue
auto v4 = mkVector<string>(""); // argument is a rvalue
案例
v3
无法编译,因为U被显式声明为U=string,因此U&&
意味着string&&
和lvalue
不兼容。
有没有办法编写函数
mkVector
,使其在所有可能的情况下都能正确工作,支持完美转发?
我能想到的最好办法是编写该函数的两个重载,但这并不理想,如果有 N 个参数而不是 1 个,则需要 2^N 个可能的重载。
template <typename U, std::enable_if_t<std::is_rvalue_reference_v<U>, bool> = true>
auto mkVector(U&& x0)
{
return std::vector<U>{std::move(x0)};
}
template <typename U>
auto mkVector(const U& x0)
{
return std::vector<U>{x0};
}
有没有办法编写函数
,使其在所有可能的情况下都能正确工作,支持完美转发?mkVector
不,没有。 您可以使用转发引用并根据用户的想法提供虚假的
U
,或者基本上在 std::forward
内实现 mkVector
的所有功能。
为此,你至少需要两次重载,所以你走在正确的轨道上,但我不会再进一步追求这个想法;这是浪费时间。
这里真正关心的是用户可以通过提供显式模板参数来破坏东西,这不仅仅是完美转发的问题。 考虑以下示例:
template <typename T>
void f(T x) {
use(std::move(x));
}
如果用户显式调用
f<U&>(u)
,则 f
将从给定的 u
移动,而不是从本地副本移动。
有许多标准库函数也没有以对显式参数(例如,std::swap
)稳健的方式实现。
这是 C++ 的一个非常基本的问题,比人们想象的要广泛得多。
那么……我们该怎么办呢?您有两个选择:
mkVector
不应该与显式参数一起使用,如果用户这样滥用它,就应该受到指责。对于后一种方法,你可以这样写:
template <typename..., typename U>
auto mkVector(U&& x0)
{
return std::vector<std::decay_t<U>>{std::forward<U>(x0)};
}
提供给
mkVector
的任何显式参数都将被 typename...
吞噬,因此用户无法再显式指定 U
。
这在技术上是可行的,但很不寻常。
大多数人只是不在乎。