c++ - 选择性转发功能

标签 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 , float .所以我的猜测是轻量级实现可能是最合适的(我不喜欢过度概括)。但我真的很想获得正确的心态来解决这些问题。

最佳答案

我感觉到您对您所面临的用例的理解存在轻微的误解。

首先,这是一个函数模板:

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 中的同一种引用转发和绑定(bind)的参数。不幸的是,这不适用于 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...不是转发引用,std::forward可以使用辅助函数代替。也就是说,取决于<T>类型在 std::forward<T> (左值引用或非左值引用)std::forward会有不同的表现:
  • 对于左值引用(例如,如果 TNoisy& ):表达式的值类别仍然是左值(即 Noisy& )。
  • 对于非左值引用(例如,如果 TNoisy&& 或普通的 Noisy ):表达式的值类别变为 xvalue(即 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的表达式的值类别成 b 的 xvalue , 即 Noisy&& .这让编译器选择移动构造函数来初始化 target 的第二个参数。功能,离开 a完整。

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

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

    现在,您还想将一些参数从一种类型转换为另一种类型,同时转发所有其他类型。如果你不关心 std::forward 的技巧参数包中的参数,并且始终调用复制构造函数很好,然后是您的版本 没问题 :
    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)...);
    

    DEMO 4

    但是,如果您想避免复制该 Noisy演示中的参数,您需要以某种方式组合 std::forward调用 process调用,越过Args类型,以便 std::forward可以应用适当的行为(变成 xvalues 或不做任何事情)。我只是给你一个简单的例子来说明如何实现:
    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 之前被调用功能(您的版本):
    get_base()->target(process(std::forward<Args>(args))...);
    

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

    而且它也能正常工作(也就是说,使用您的版本)。所以重点是,额外的 std::forward只是稍微优化一下你的代码,还有provided idea只是该功能的可能实现之一(如您所见,它带来了相同的效果)。

    关于c++ - 选择性转发功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27869997/

    相关文章:

    c++ - 使用临时函数对象进行全局初始化

    c++ - 使用带有转发引用的 std::forward

    java - 泽西网址转发

    c++ - Catch(...) with unknown object - 我如何识别抛出的是什么?

    c++ - 循环终止时无法获得输入

    c++ - 有问题的代码???我的析构函数有问题吗?

    c++ - SFINAE std::isfinite 和使用 std::is_arithmetic 的类似函数

    c++ - uniform_int_distribution a()、b()、min() 和 max()

    https - 客户端防火墙阻止除 80 和 443 以外的所有端口,需要将端口 443 上的请求转发到 SSH 或 HTTPS

    c++ - 为什么这个 boost::variant 缺少运算符 <<?