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

时间:2015-01-04 15:34:39

标签: c++ c++11 move

最近我经常遇到这个问题,我必须编写一个将输入作为const引用的函数。但在某些时候,这个函数(通常是一个构造函数)调用另一个可以使用输入作为移动参考的函数。出于这个原因,我通常创建一个函数的副本,以允许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成为一个tempalte函数并使用std::forward,但我不想这样做,因为它允许将任何参数类型传递给bar (特别是因为在我的实际应用程序栏中是隐式构造函数)。

所以我的问题是:有没有其他方法可以通过函数转发移动引用而无需编写两次?

3 个答案:

答案 0 :(得分:4)

如果要在单个函数中同时接受rvalues和lvalues,保留恢复其参数的值类别的可能性,则可以使用 forwarding-reference 。您可以使用表达式SFINAE 技术轻松限制传递的参数类型,在您的情况下,这将验证调用foo(std::forward<T>(a))是否格式正确。如果不是,那么在重载决策期间,该函数将被排除在可行函数集之外:

选项#1

在尾随返回类型中隐藏表达式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

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

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

DEMO 3

答案 1 :(得分:2)

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

答案 2 :(得分:1)

一个选项是添加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);
}