如何存储通用引用

How to store universal references

本文关键字:引用 存储 何存储      更新时间:2023-10-16

我需要将通用引用存储在类中(我相信引用的值会比类更持久)。有规范的方法吗?

以下是我所想到的一个最简单的例子。这似乎有效,但我不确定我是否做对了。

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)

你不能"存储通用引用",因为没有这样的东西,只有右值引用和左值引用。"通用引用"是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&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&&

binder(F&& f, X&& x)运行良好。

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

除此之外,代码看起来很好(只要你真的能确保引用的变量比绑定器活得更长)。