8

我需要将通用引用存储在一个类中(我确信引用的值会比这个类更长寿)。有这样做的规范方法吗?

这是我想出的一个最小示例。它似乎有效,但我不确定我是否做对了。

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).

4

2 回答 2

7

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

查看代码的具体细节:

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

在这里,您binder使用引用类型作为模板参数进行实例化,因此在类定义中无需将成员声明为右值引用,因为它们已经引用类型(由 推导的左值或右值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&and X&(或者在你总是用引用参数实例化类型的情况下,Fand X

所以我会将代码简化为:

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&&
于 2013-02-07T22:19:53.463 回答
1

binder(F&& f, X&& x) 工作正常

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

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

于 2013-02-07T17:51:50.850 回答