如何存储通用引用

mar*_*n78 7 c++ templates perfect-forwarding c++11 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&&
}
Run Code Online (Sandbox Code Playgroud)

我的推理是正确的还是会导致微妙的错误?另外,是否有更好(即更简洁)的方式来编写构造函数?binder(F&& f, X&& x)不起作用,因为那些是r值参考,因此不允许binder(f, i).

Jon*_*ely 5

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

要查看代码的具体细节:

template <typename F, typename X>
binder<F&&, X&&> bind(F&& f, X&& x)
Run Code Online (Sandbox Code Playgroud)

在这里,您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;
};
Run Code Online (Sandbox Code Playgroud)

在这个版本中类型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));
}
Run Code Online (Sandbox Code Playgroud)

现在,您binder使用左值引用类型或对象(即非引用)类型进行实例化,然后在内部binder使用附加类型声明成员,&&以便它们是左值引用类型或右值引用类型.

此外,如果您考虑一下,则无需存储右值引用成员.通过左值引用存储对象是完全正确的,所有重要的是你它们正确地转发operator()函数中的左值或右值.所以类成员可能只是F&X&(或者在你始终使用引用参数实例化类型的情况下,F并且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);
}
Run Code Online (Sandbox Code Playgroud)

这个版本保留了所需类型的模板参数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&&
Run Code Online (Sandbox Code Playgroud)