显式声明模板参数时如何进行完美转发

问题描述 投票:0回答:1

考虑这个简单的功能

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};
}
c++ templates c++17 move-semantics perfect-forwarding
1个回答
0
投票

有没有办法编写函数

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
。 这在技术上是可行的,但很不寻常。 大多数人只是不在乎。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.