选择性转发功能

时间:2015-01-09 22:04:58

标签: c++ c++11 forwarding perfect-forwarding

任务是创建一个单参数函数,该函数转发除一个(Foo)之外的所有类型(它转换为Bar)。

(我们假设存在从Foo到Bar的转换)。

以下是使用方案:

template<typename Args...>
void f( Args... args )
{
    g( process<Args>(args)... );
}

(我试图从原始背景here中提取/简化它。 - 如果我犯了错误,请有人告诉我!)

以下是两种可能的实施方式:

template<typename T>
T&& process(T&& t) { 
    return std::forward<T>(t); 
}

Bar process(Foo x) { 
    return Bar{x}; 
}

和...

template <typename T, typename U>
T&& process(U&& u) {
    return std::forward<T>(std::forward<U>(u));
}

template <typename T>
Bar process(Foo x) {
    return Bar{x};
} 

我有良好的权威(here),第二个更好。

但是,我无法理解给出的解释。我认为这是深入研究C ++中一些最黑暗的角落。

我认为我缺少必要的机器来理解发生了什么。有人可以详细解释一下吗?如果挖掘太多,是否有人可以推荐一种资源来学习必要的先决条件概念?

编辑:我想补充一点,在我的特定情况下,函数签名将匹配this page上的一个typedef-s。也就是说,每个参数都将是PyObject*PyObject是普通的C结构)或某些基本的C类型,如const char*int,{{ 1}}。所以我的猜测是轻量级实现可能是最合适的(我不是过度推广的粉丝)。但我真的很想获得正确的思维方式来解决这些问题。

2 个答案:

答案 0 :(得分:2)

在您理解您面临的用例时,我感到一种轻微的误解。

首先,这是一个功能模板:

struct A
{
    template <typename... Args>
    void f(Args... args)
    {
    }
};

这不是功能模板:

template <typename... Args>
struct A
{
    void f(Args... args)
    {
    }
};

在前一个定义(带有函数模板)中,发生了参数类型推导。在后者中,没有类型扣除。

您没有使用功能模板。您正在使用类模板中的非模板成员函数,对于此特定成员函数,其签名是固定的。

通过定义您的trap类,如下所示:

template <typename T, T t>
struct trap;

template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{    
    static R call(Args... args);
};

并参考其成员函数如下:

&trap<decltype(&Base::target), &Base::target>::call;

您最终得到一个指向静态非模板call函数的指针,该函数具有固定签名,与target函数的签名相同。

现在,call函数充当中间调用者。您将调用call函数,该函数将调用target成员函数,传递其自己的参数以初始化target的参数,例如:

template <typename R, typename... Args, R(Base::*t)(Args...)>
struct trap<R(Base::*)(Args...), t>
{    
    static R call(Args... args)
    {
        return (get_base()->*t)(args...);
    }
};

假设用于实例化target类模板的trap函数定义如下:

struct Base
{
    int target(Noisy& a, Noisy b);
};

通过实例化trap类,您最终得到以下call函数:

// what the compiler *sees*
static int call(Noisy& a, Noisy b)
{
    return get_base()->target(a, b);
}

幸运的是,a通过引用传递 ,它只是被target参数中的相同类型的引用转发和绑定。不幸的是,这并不适用于b对象 - 无论Noisy类是否可移动,您都在制作b实例的多个副本,因为那个通过值传递

  • 第一个:当从外部上下文调用call函数时。

  • 第二个:在b正文中调用target函数时复制call个实例。

DEMO 1

这有点效率低下:您可以保存至少一个复制构造函数调用,只要您将b实例转换为 xvalue ,就可以将其转换为移动构造函数调用:

static int call(Noisy& a, Noisy b)
{
    return get_base()->target(a, std::move(b));
    //                           ~~~~~~~~~~~^
}

现在它将调用移动构造函数代替第二个参数。

到目前为止一直很好,但这是手动完成的(std::move已经知道应用移动语义是安全的)。现在,问题是,在参数包上运行时,如何应用相同的功能?:

return get_base()->target(std::move(args)...); // WRONG!

您无法对参数包中的每个参数应用std::move调用。如果同样适用于所有参数,这可能会导致编译器错误。

DEMO 2

幸运的是,即使Args...不是 forwarding-reference ,也可以使用std::forward辅助函数。也就是说,根据<T> std::forward<T>类型(左值引用或非左值引用)的含义,std::forward的行为会有所不同:

  • 对于左值引用(例如,如果TNoisy&):表达式的值类别仍然是左值(即Noisy&)。

  • 对于非左值引用(例如,如果TNoisy&&或普通Noisy):表达式的值类别变为x值(即{{1 }})。

话虽如此,通过定义Noisy&&函数如下所示:

target

你最终得到:

static R call(Args... args)
{
    return (get_base()->*t)(std::forward<Args>(args)...);
} 

将涉及static int call(Noisy& a, Noisy b) { // what the compiler *sees* return get_base()->target(std::forward<Noisy&>(a), std::forward<Noisy>(b)); } 的表达式的值类别转换为b的x值,即b。这允许编译器选择移动构造函数来初始化Noisy&&函数的第二个参数,使target保持不变。

DEMO 3 (将输出与DEMO 1进行比较)

基本上,这就是a的用途。通常,std::forward forwarding-reference 一起使用,其中std::forward包含根据转发引用的类型推导规则推导出的类型。请注意,它始终要求您明确地传递T部分,因为它将根据该类型应用不同的行为(不依赖于其参数的值类别)。如果没有显式类型模板参数<T><T>将总是推导出通过其名称引用的参数的左值引用(例如扩展参数包时)。

现在,您希望另外将一些参数从一种类型转换为另一种类型,同时转发所有其他类型。如果你不关心参数包中std::forward参数的技巧,并且总是调用一个拷贝构造函数,那么你的版本就可以了

std::forward

DEMO 4

但是,如果您想在演示中避免复制template <typename T> // transparent function T&& process(T&& t) { return std::forward<T>(t); } Bar process(Foo x) { // overload for specific type of arguments return Bar{x}; } //... get_base()->target(process(args)...); 参数,则需要以Noisy调用与std::forward调用,传递process类型,以便Args可以应用正确的行为(变成xvalues或不做任何事情)。我刚刚给你一个如何实现这个的简单例子:

std::forward

但这只是其中一个选择。它可以简化,重写或重新排序,以便在调用template <typename T, typename U> T&& process(U&& u) { return std::forward<T>(std::forward<U>(u)); } template <typename T> Bar process(Foo x) { return Bar{x}; } //... get_base()->target(process<Args>(args)...); 函数(您的版本)之前调用std::forward

process

DEMO 5 (将输出与DEMO 4进行比较)

它也可以正常工作(也就是说,你的版本)。所以关键是,额外的get_base()->target(process(std::forward<Args>(args))...); 只是为了优化你的代码,而provided idea只是该功能的可能实现之一(正如你所看到的,它带来了同样的效果) )。

答案 1 :(得分:0)

第2版的第一部分是否足够?只有:

template <typename T, typename U>
T&& process(U&& u) {
    return std::forward<T>(std::forward<U>(u));
}

给出现有转换的用例(来自“Foo”的“Bar”构造函数),如:

struct Foo {
    int x;
};
struct Bar {
    int y;
    Bar(Foo f) {
        y = f.x;
    }
};
int main() {

    auto b = process<Bar>(Foo()); // b will become a "Bar"
    auto i = process<int>(1.5f);
}

您无论如何都必须指定第一个模板参数(要转换的类型),因为编译器无法推断它。所以它知道你期望什么类型,并将构造一个类型为“Bar”的临时对象,因为它有一个构造函数。