使用rvalue引用而不是使用可变参数模板转发引用

bol*_*lov 1 c++ rvalue-reference variadic-templates c++14 forwarding-reference

我有一个结构X和一个foo必须接收rvalue引用的函数X.

起初我只用一个参数开始,这很简单(哦......更简单的时间):

auto foo(X&& arg) -> void {...};

X x;
foo(x);            // compile error [OK]
foo(std::move(x)); // accepted      [OK]
foo(X{});          // accepted      [OK]
Run Code Online (Sandbox Code Playgroud)

但后来我想扩展并接受可变数量的X参数(仍然只是rvalue rferences).

但有一个问题.

  • 1,你不可能有auto foo(X&&... args)理想的
  • 2现在你被迫做了,template <class... Args> auto foo(Args&&... args)但现在你最终得到转发参考,这将很乐意接受非临时性的:
template <class... Args>
auto foo(Args&&... args) -> void { ... };

X x1, x2;
foo(x1, x2);                       // accepted [NOT OK]
foo(std::move(x1), std::move(x2)); // accepted [OK]
foo(X{}, X{});                     // accepted [OK]
Run Code Online (Sandbox Code Playgroud)

为什么他们使用这种语法和规则来转发引用因为乞讨而困惑我.这是一个问题.这种语法的另一个问题是,T&&并且X<T>&&是完全不同的野兽.但是我们在这里偏离轨道.

我知道如何与解决这个static_assert或者SFINAE但是这两种解决方案的复杂的事情了一下,在我的愚见不应该被需要的,如果语言是正确的一次设计.甚至不让我开始std::initializer_list...我们再次偏离轨道.

所以我的问题是:是否有一个简单的解决方案/技巧我被女巫遗失Args&&/ args被视为右值引用?


正如我正在回答这个问题,我认为我有一个解决方案.

为左值引用添加已删除的重载:

template <class... Args>
auto foo(const Args&... args) = delete;

template <class... Args>
auto foo(Args&... args) = delete;
Run Code Online (Sandbox Code Playgroud)

简单,优雅,应该工作,让我们测试一下:

X x1, x2;

foo(x1, x2);                       // compile error [OK]
foo(std::move(x1), std::move(x2)); // accepted [OK]
foo(X{}, X{});                     // accepted [OK]
Run Code Online (Sandbox Code Playgroud)

好的,是的,我拥有它!

foo(std::move(x1), x2);            // accepted [oh c'mon]
Run Code Online (Sandbox Code Playgroud)

Bar*_*rry 5

有一堆右值引用的方法是用SFINAE:

template <class... Args,
    std::enable_if_t<(!std::is_lvalue_reference<Args>::value && ...), int> = 0>
void foo(Args&&... args) { ... }
Run Code Online (Sandbox Code Playgroud)

折叠表达式是 C++17,很容易编写一个元函数来在 C++14 中获得相同的行为。这实际上是您唯一的选择 - 您需要一个约束函数模板来推导右值引用,但唯一可用的语法被重载以表示转发引用。我们可以使模板参数不被推导,但是您必须提供它们,这似乎根本不是解决方案。

有了概念,这当然更清晰,但我们并没有真正改变底层机制:

template <class... Args>
    requires (!std::is_lvalue_reference<Args>::value && ...)
void foo(Args&&... args) { ... }
Run Code Online (Sandbox Code Playgroud)

或更好:

template <class T>
concept NonReference = !std::is_lvalue_reference<T>::value;

template <NonReference... Args>
void foo(Args&&... ) { ... }
Run Code Online (Sandbox Code Playgroud)

值得指出的是,这些都不起作用:

template <class... Args> auto foo(const Args&... args) = delete;
template <class... Args> auto foo(Args&... args) = delete;
Run Code Online (Sandbox Code Playgroud)

因为它们只删除采用所有左值引用的重载,而您想要删除采用任何左值引用的重载。

  • 这就是我害怕的。我会选择`std::is_rvalue_reference&lt;Args&amp;&amp;&gt;::value`(或者更好的`std::is_same&lt;Args&amp;&amp;, X&amp;&amp;&gt;::value`)。您选择 `!std::is_lvalue_reference&lt;Args&gt;` 有什么原因吗? (2认同)

sky*_*ack 5

我知道如何使用static_assert或SFINAE来解决这个问题,但是这两个解决方案都有点复杂,因为有一个简单的解决方案/技巧我不知道了[...]?

在没有复杂的SFINAE表达式或static_asserts的情况下,有一种非常好的方法可以做到这一点.它也没有要求你猜测哪些参数是const和什么不是(如果你尝试使用变量的常量,它可以很快引导你进入UB).此外<type_traits>,如果你关心这一点,你不必为此而包括在内.
它基于@ Justin的答案几乎就在那里.

我们来看看代码.只需使用decltype和您只需要声明的测试函数,根本没有定义:

#include<utility>

template<typename... T>
void test(const T &&...);

template <class... Args>
auto foo(Args&&... args)
-> decltype(test(std::forward<Args>(args)...), void())
{}

struct X {};

int main() {
    X x1, x2;
    //foo(x1, x2);
    foo(std::move(x1), std::move(x2));
    foo(X{}, X{});
}
Run Code Online (Sandbox Code Playgroud)

如果切换注释,则示例将不再按要求进行编译.

在另一个答案中讨论的基本思想几乎相同:如果这是一个右值引用,你可以将它分配给一个const右值引用.无论如何,因为你不想让const那些原本不是const的参数,只需将它们一起测试然后再使用原始参数.