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).
但有一个问题.
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]
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)
该有一堆右值引用的方法是用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)
因为它们只删除采用所有左值引用的重载,而您想要删除采用任何左值引用的重载。
我知道如何使用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的参数,只需将它们一起测试然后再使用原始参数.