【问题标题】:Perfect forwarding of variading template arguments可变模板参数的完美转发
【发布时间】:2013-01-06 12:59:01
【问题描述】:

我为通用信号/插槽系统编写了以下实现:

template< typename... Args >
class Signal : NonCopyable
{
public:

    typedef std::function< void (Args...) > Delegate;

    void connect(const Delegate& delegate);
    void operator ()(Args&&... args) const;

private:

    std::list< Delegate > _delegates;
};

template< typename... Args >
void Signal< Args... >::connect(const Delegate& delegate)
{
    _delegates.push_front(delegate);
}

template< typename... Args >
void Signal< Args... >::operator ()(Args&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args >(args)...);
}

之后,我使用以下简单案例测试了我的课程:

Signal< int > signal;

// Case 1
signal(0);

//Case 2
int i(0);
signal(i);

案例 1 编译没有问题。另一方面,情况 2 在 GCC 4.7.2 下会产生以下错误:

/home/pmjobin/Workspace/main.cpp:1196:10: error: cannot bind ‘int’ lvalue to ‘int&&’
/home/pmjobin/Workspace/main.cpp:82:6: error:   initializing argument 1 of ‘void Signal<Args>::operator()(Args&& ...) const [with Args = {int}]’

我知道这个问题与完美转发有关(以及我对后者的误解)。但是,我从 std::make_shared() 和 std::make_tuple() 实现中得到了启发,我看不到将可变参数转发给代表的方式有什么不同。唯一显着的区别是 make_shared() 和 make_tuple() 都是函数模板,而不是像上面的 Signal 实现这样的类模板。

- 编辑-

针对各种 cmets,这里有一个新版本的 Signal 类实现,它不会遇到上述问题。此外,现在可以使用连接函数返回的不透明令牌断开委托。结果可能不像其他实现(例如 boost::signal)那样灵活和强大,但至少它具有简单和轻量级的优点。

template< typename Signature >
class Signal : NonCopyable
{
public:

    typedef std::function< Signature > Delegate;

    class DisconnectionToken
    {
        DisconnectionToken(typename std::list< Delegate >::iterator it)
            : _it(it)
        {}

        typename std::list< Delegate >::iterator _it;

        friend class Signal;
    };

    DisconnectionToken connect(const Delegate& delegate);
    void disconnect(DisconnectionToken& token);

    template< typename... Args >
    void operator ()(Args&&... args) const;

private:

    std::list< Delegate > _delegates;
};

template< typename Signature >
typename Signal< Signature >::DisconnectionToken Signal< Signature >::connect(const Delegate& delegate)
{
    _delegates.push_front(delegate);
    return DisconnectionToken(_delegates.begin());
}

template< typename Signature >
void Signal< Signature >::disconnect(DisconnectionToken& token)
{
    if (token._it != _delegates.end())
    {
        _delegates.erase(token._it);
        token._it = _delegates.end();
    }
}

template< typename Signature >
template< typename... Args >
void Signal< Signature >::operator ()(Args&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args >(args)...);
}

【问题讨论】:

  • Args&amp;&amp;... args 基本上是int &amp;&amp; args。
  • @Nawaz 是的,我错过了他在上面明确指定的内容。别管我刚才说的。是的,完美转发主要只在你让模板类型被推断出来时才有效。
  • OP 需要(或可以)做的是为operator() 提供一个单独的Args...。
  • @SethCarnegie:即使这样设计也有缺陷。完美的转发和std::function 的列表不会很顺利。
  • @SethCarnegie:完美转发是重载函数的解决方案,它接受参数为X&amp;&amp; 和X const&amp;(例如)。

标签: c++ c++11 variadic-templates perfect-forwarding


【解决方案1】:

问题在于您将模板参数明确指定为int。正如 Nawaz 所提到的,Args&amp;&amp;... 会扩展为 int&amp;&amp;,您不能将左值绑定到右值引用。

完美转发起作用的原因是,当您调用一个函数(例如)而不指定模板参数时,它们会被推导出为&amp; 或&amp;&amp;,然后引用崩溃(如果您不指定,请阅读参考崩溃不知道那是什么)。您确实明确指定了它,因此您禁止引用折叠并将其搞砸。

你可以做的一件事是给operator()它自己的模板参数列表:

template<typename... Args2>
void operator ()(Args2&&... args) const;

...

template< typename... Args >
template< typename... Args2 >
void Signal< Args... >::operator ()(Args2&&... args) const
{
    for (const Delegate& delegate : _delegates)
        delegate(std::forward< Args2 >(args)...);
}

这样你可以让参考折叠为你处理它。

【讨论】:

  • 同样的问题:现在如果我将s声明为Signal&lt;int&amp;&amp;&gt; s;,然后将其调用为int i =10; s(i);,那么它将无法编译!
  • @Nawaz 然后不要将s 声明为Signam&lt;int&amp;&amp;&gt;
  • @Nawaz 你可以调用兼容的函数。你不能像int i; s(i);那样调用函数void s(int&amp;&amp;),否则你也不能在这里做。
  • 如果我们有一个函数void s(X&amp;&amp;),那么我们也有它的重载void s(X const &amp;),例如。所以我们可以将s 称为:s(X()); 和X x; s(x); 两者!
  • @SethCarnegie:他真正需要做的是停止让Signal 使用可变参数模板。做 Boost.Signal 所做的事情,让 Signal 采用函数类型 ala std::function 的模板参数。这样,operator() 就是可变参数模板所在的位置。
猜你喜欢
  • 1970-01-01
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-01-06
  • 1970-01-01
相关资源
最近更新 更多