如何存储通用引用

时间:2013-02-07 17:40:58

标签: c++ templates c++11 perfect-forwarding universal-reference

我需要在类中存储通用引用(我确信引用的值将比类更长)。有没有规范的方法呢?

这是我提出的最小例子。它似乎有效,但我不确定我是否做对了。

template <typename F, typename X>
struct binder
{
    template <typename G, typename Y>
    binder(G&& g, Y&& y) : f(std::forward<G>(g)), x(std::forward<Y>(y)) {}

    void operator()() { f(std::forward<X>(x)); }

    F&& f;
    X&& x;
};

template <typename F, typename X>
binder<F&&, X&&> bind(F&& f, X&& x)
{
    return binder<F&&, X&&>(std::forward<F>(f), std::forward<X>(x));
}

void test()
{
    int i = 1;
    const int j = 2;
    auto f = [](int){};

    bind(f, i)();   // X&& is int&
    bind(f, j)();   // X&& is const int&
    bind(f, 3)();   // X&& is int&&
}

我的推理是正确的还是会导致微妙的错误?另外,编写构造函数的方法是否更好(即更简洁)? binder(F&& f, X&& x)无效,因为这些是r值引用,因此不允许binder(f, i)

2 个答案:

答案 0 :(得分:5)

你不能“存储通用引用”,因为没有这样的东西,只有rvalue引用和左值引用。 “通用参考”是Scott Meyers描述语法特征的方便术语,但它不是类型系统的一部分

查看代码的具体细节:

template <typename F, typename X>
binder<F&&, X&&> bind(F&& f, X&& x)

在这里,您使用引用类型将binder实例化为模板参数,因此在类定义中不需要将成员声明为rvalue-references,因为它们已经 引用类型(由bind推导出的左值或右值)。这意味着您总是拥有比所需更多的&&个令牌,这些令牌是多余的,并且由于参考折叠而消失。

如果您确定binder将始终由bind函数实例化(因此始终使用引用类型进行实例化),那么您可以将其定义为:

template <typename F, typename X>
struct binder
{
    binder(F g, X y) : f(std::forward<F>(g)), x(std::forward<X>(y)) {}

    void operator()() { f(std::forward<X>(x)); }

    F f;
    X x;
};

在此版本中,FX类型是引用类型,因此使用F&&X&&是多余的,因为它们已经是左值引用(因此&&什么都不做)或者它们是左值引用(因此&&在这种情况下也不做任何事情!)

或者,您可以保留binder,并将bind更改为:

template <typename F, typename X>
binder<F, X> bind(F&& f, X&& x)
{
    return binder<F, X>(std::forward<F>(f), std::forward<X>(x));
}

现在您使用左值引用类型或对象(即非引用)类型实例化binder,然后在binder内部声明具有附加&&的成员,因此它们要么是左值引用类型或右值引用类型。

此外,如果您考虑一下,则无需存储右值引用成员。通过左值引用存储对象是完美的,所有重要的是你正确地将它们转发作为operator()函数中的左值或右值。因此,类成员可能只是F&X&(或者在您始终使用引用参数实例化类型的情况下,FX

所以我会将代码简化为:

template <typename F, typename X>
struct binder
{
    binder(F& g, X& y) : f(g), x(y) { }

    void operator()() { f(std::forward<X>(x)); }

    F& f;
    X& x;
};

template <typename F, typename X>
binder<F, X> bind(F&& f, X&& x)
{
    return binder<F, X>(f, x);
}

此版本会在模板参数FX中保留所需类型,并在std::forward<X>(x)表达式中使用正确的类型,这是唯一需要它的位置。

最后,我发现根据推导出的类型进行思考更准确,更有帮助,而不仅仅是(折叠)引用类型:

bind(f, i)();   // X is int&, X&& is int&
bind(f, j)();   // X is const int&, X&& is const int&
bind(f, 3)();   // X is int, X&& is int&&

答案 1 :(得分:1)

binder(F&& f, X&& x) works fine

FX是引用类型(它们是模板实例化binder<F&&, X&&>中提供的类型),因此fx成为普遍崇敬遵循通常的参考折叠规则。

除此之外,代码看起来很好(只要您确实可以确保引用的变量比活页夹更长)。