完美转发变量模板参数

时间:2013-01-23 05:32:05

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

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

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()&amp; std :: make_tuple()实现,我没有看到我将可变参数转发给委托的方式有任何不同。唯一值得注意的区别是make_shared()和make_tuple()都是函数模板而不是类模板,例如上面的Signal实现。

- 编辑 -

响应各种评论,这里是Signal类实现的新版本,它没有遇到上述问题。此外,现在可以使用connect函数返回的opaque标记断开委托。结果可能不像其他实现那样灵活和强大(例如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)...);
}

1 个答案:

答案 0 :(得分:5)

问题是您明确将模板参数指定为int。正如Nawaz所提到的,Args&&...被扩展为int&&,你无法将左值绑定到右值参考。

完美转发的原因在于,当您在未指定模板参数的情况下调用函数(例如)时,它们会被推导为&&&,然后引用会崩溃(请参阅参考资料)如果你不知道那是什么,就会崩溃。你但是明确地指定了它,所以你禁止参考折叠并将它全部搞砸。

你可以做的一件事就是给它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)...);
}

通过这种方式,您可以让参考折叠为您处理。