通过函数转发移动引用,无需编写两次函数

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));
}

DEMO 1

选项 #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));
}

DEMO 2

后一种方法对构造函数(不指定 return 类型)特别有用:

struct Bar
{
    template <typename T,
              typename = decltype(foo(std::forward<T>(std::declval<T&>())))>
    Bar(T&& a)
    {
        foo(std::forward<T>(a));
    }
};

DEMO 3

使用 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.