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

时间:2017-08-04 19:22:12

标签: c++ c++14 variadic-templates rvalue-reference forwarding-reference

我有一个结构X和一个函数foo,它必须接收X的右值引用。

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

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

X x;
foo(x);            // compile error [OK]
foo(std::move(x)); // accepted      [OK]
foo(X{});          // accepted      [OK]

但后来我想扩展并接受可变数量的X个参数(仍然只有右值的参数)。

但是有一个问题。

  • 1,你不能拥有理想的auto foo(X&&... args)
  • 第二,现在你被迫做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]

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

我知道如何使用static_assertSFINAE来解决这个问题,但这两种解决方案都会使事情变得复杂,而且如果语言设计正确,那么我的拙见绝不需要。甚至不让我开始std::initializer_list ......我们又开始偏离轨道了。

所以我的问题:巫婆Args&& / args是否有一个简单的解决方法/技巧被视为右值参考?

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

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

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

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

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

X x1, x2;

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

好的,是的,我有它!

foo(std::move(x1), x2);            // accepted [oh c'mon]

3 个答案:

答案 0 :(得分:5)

  

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

如果没有复杂的SFINAE表达式或static_assert s,你可以做到这一点。它也没有要求你猜测哪些参数是const和什么不是(如果你尝试使用变量的常量,它可以很快引导你进入UB)。此外,如果您关心这一点,则不必包括<type_traits> 它基于@Justin's answer几乎就在那里。

让我们看看代码。只需使用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{});
}

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

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

答案 1 :(得分:4)

有一堆右值参考的方法是SFINAE:

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

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

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

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

或更好:

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

template <NonReference... Args>
void foo(Args&&... ) { ... }

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

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

因为它们只删除了带有所有左值引用的重载,并且您想要删除带有任何左值引用的重载。

答案 2 :(得分:1)

如果确实想要避免使用SFINAE,您可以通过const&&获取参数;他们不会成为普遍参考:

template <class... Args>
auto foo(const Args&&... args) -> void { ... };

const&&只能绑定到右值,而不能绑定左值。

#include <iostream>

template <class... Args>
auto foo(const Args&&... args) -> void
{
    std::cout << __PRETTY_FUNCTION__ << '\n';
}

struct X {};

int main () {
    X x1, x2;
    // foo(x1, x2);                    // compile error
    foo(std::move(x1), std::move(x2)); // accepted [OK]
    // foo(std::move(x1), x2);         // compile error
    foo(X{}, X{});                     // accepted [OK]
}

On Coliru

不幸的是,您必须抛弃const,因此它不是最漂亮的解决方案。