通过函数转发移动引用,无需编写两次函数
Forward a move reference through a function, without writing the function twice
最近我经常遇到一个问题,我不得不写一个函数,它把一个输入作为常量引用。但在某些时候,此函数(通常是构造函数)调用另一个函数,该函数可以使用输入作为移动参考。出于这个原因,我通常创建一个函数的副本以允许 const 引用和移动引用,即
#include <iostream>
class A {};
void foo(const A& a) { std::cout << "Reference" << std::endl; }
void foo( A&& a) { std::cout << "Move" << std::endl; }
void bar(const A& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
void bar(A&& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
int main() {
A a;
bar(a);
bar(A());
}
然而,复制bar
两次显然很难看,唯一的区别是签名和std::move
。我知道的一种替代方法是使 bar
成为模板函数并使用 std::forward
,但我不想这样做,因为它允许将任何参数类型传递给 bar
(特别是因为在我的真实应用程序中 bar 是一个隐式构造函数)。
所以我的问题是:有没有其他方法可以通过函数转发移动引用而不用写两次?
一个选项是添加 bar
的模板化版本,该版本采用指向 foo
的指针并包含当前 bar
的现有实现中的所有通用代码。
template<class T>
void bar(T&& a, void (*f)(T&&a))
{
//Other things, which treat "a" as a const reference
f(std::move(a));
}
void bar(const A& a)
{
bar<const A&>(a, foo);
}
void bar(A&& a)
{
bar(std::move(a), foo);
}
如果您想在单个函数中同时接受右值和左值,保留恢复其参数值类别的可能性,您可以使用 转发引用。您可以使用 expression SFINAE 技术轻松限制传递的参数类型,在您的情况下,这将验证调用 foo(std::forward<T>(a))
是否格式正确。如果不是,该函数将在重载决议期间从可行函数集中排除:
选项#1
在尾部 return 类型中隐藏 表达式 SFINAE:
template <typename T>
auto bar(T&& a)
-> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
选项 #2
在模板参数列表中隐藏 表达式 SFINAE:
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
void bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
后一种方法对构造函数(不指定 return 类型)特别有用:
struct Bar
{
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
Bar(T&& a)
{
foo(std::forward<T>(a));
}
};
使用 SFINAE 进行完美转发并保持过载集不受污染。您首先必须决定究竟应该检查什么,例如应该为应该有效的表达式调用此模板的类型集。
在这里,两者都足够了 - 此代码检查类型:
// Helper template:
template <typename T, typename U, typename R=void>
using enable_if_compatible = typename std::enable_if<std::is_same<U,
typename std::remove_cv<
typename std::remove_reference<T>::type>::type>::value, R>::type;
// Possible usage:
template <typename T>
enable_if_compatible<T, A> bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.
下面一个取决于对foo
调用的有效性,应该更灵活。
template <typename T>
auto bar(T&& a) -> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.
最近我经常遇到一个问题,我不得不写一个函数,它把一个输入作为常量引用。但在某些时候,此函数(通常是构造函数)调用另一个函数,该函数可以使用输入作为移动参考。出于这个原因,我通常创建一个函数的副本以允许 const 引用和移动引用,即
#include <iostream>
class A {};
void foo(const A& a) { std::cout << "Reference" << std::endl; }
void foo( A&& a) { std::cout << "Move" << std::endl; }
void bar(const A& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
void bar(A&& a) {
//Other things, which treat "a" as a const reference
foo(std::move(a));
}
int main() {
A a;
bar(a);
bar(A());
}
然而,复制bar
两次显然很难看,唯一的区别是签名和std::move
。我知道的一种替代方法是使 bar
成为模板函数并使用 std::forward
,但我不想这样做,因为它允许将任何参数类型传递给 bar
(特别是因为在我的真实应用程序中 bar 是一个隐式构造函数)。
所以我的问题是:有没有其他方法可以通过函数转发移动引用而不用写两次?
一个选项是添加 bar
的模板化版本,该版本采用指向 foo
的指针并包含当前 bar
的现有实现中的所有通用代码。
template<class T>
void bar(T&& a, void (*f)(T&&a))
{
//Other things, which treat "a" as a const reference
f(std::move(a));
}
void bar(const A& a)
{
bar<const A&>(a, foo);
}
void bar(A&& a)
{
bar(std::move(a), foo);
}
如果您想在单个函数中同时接受右值和左值,保留恢复其参数值类别的可能性,您可以使用 转发引用。您可以使用 expression SFINAE 技术轻松限制传递的参数类型,在您的情况下,这将验证调用 foo(std::forward<T>(a))
是否格式正确。如果不是,该函数将在重载决议期间从可行函数集中排除:
选项#1
在尾部 return 类型中隐藏 表达式 SFINAE:
template <typename T>
auto bar(T&& a)
-> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
选项 #2
在模板参数列表中隐藏 表达式 SFINAE:
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
void bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
后一种方法对构造函数(不指定 return 类型)特别有用:
struct Bar
{
template <typename T,
typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
Bar(T&& a)
{
foo(std::forward<T>(a));
}
};
使用 SFINAE 进行完美转发并保持过载集不受污染。您首先必须决定究竟应该检查什么,例如应该为应该有效的表达式调用此模板的类型集。
在这里,两者都足够了 - 此代码检查类型:
// Helper template:
template <typename T, typename U, typename R=void>
using enable_if_compatible = typename std::enable_if<std::is_same<U,
typename std::remove_cv<
typename std::remove_reference<T>::type>::type>::value, R>::type;
// Possible usage:
template <typename T>
enable_if_compatible<T, A> bar(T&& a)
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.
下面一个取决于对foo
调用的有效性,应该更灵活。
template <typename T>
auto bar(T&& a) -> decltype(void(foo(std::forward<T>(a))))
{
//Other things, which treat "a" as a const reference
foo(std::forward<T>(a));
}
Demo.