我有一个结构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
个参数(仍然只有右值的参数)。
但是有一个问题。
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_assert
或SFINAE
来解决这个问题,但这两种解决方案都会使事情变得复杂,而且如果语言设计正确,那么我的拙见绝不需要。甚至不让我开始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]
答案 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]
}
不幸的是,您必须抛弃const
,因此它不是最漂亮的解决方案。