Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perfect forwarding of variading template arguments

I've written the following implementation for a generic signal/slot system:

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

Afterwards, I tested my class using the following simple cases:

Signal< int > signal;

// Case 1
signal(0);

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

Case 1 compiles without issue. Case 2, on the other hand, generates the following error under 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}]’

I understand the issue has something to do with perfect-forwarding (and my misunderstanding of the latter). However, I've inspired myself from the std::make_shared() & std::make_tuple() implementations and I fail to see any difference in the way I forward the variadic arguments to the delegates. The only notable difference being that both make_shared() and make_tuple() are function templates rather than a class template such as the Signal implementation above.

- EDIT -

In response to the various comments, here is a new version of the Signal class implementation which doesn't suffer from the aforementioned issues. Additionally, it is now possible to disconnect delegates using an opaque token returned by the connect function. The result might not be as flexible and powerful as other implementations out there (such as boost::signal), but at least, it has the benefit of being simple and lightweight.

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)...);
}
like image 519
pmjobin Avatar asked Jan 23 '13 05:01

pmjobin


People also ask

Which is a correct example of template parameters?

For example, given a specialization Stack<int>, “int” is a template argument. Instantiation: This is when the compiler generates a regular class, method, or function by substituting each of the template's parameters with a concrete type.

How do you use perfect forwarding?

To summarise, for perfect forwarding to work it requires that a template function accept a parameter of type T&& and that template type deduction is used. As well as this std::forward must have its type explicitly defined.

What is perfect forwarding?

If a function templates forward its arguments without changing its lvalue or rvalue characteristics, we call it perfect forwarding.

What are template arguments?

In C++ this can be achieved using template parameters. A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


1 Answers

The problem is that you're explicitly specifying the template parameter as an int. As Nawaz mentioned, Args&&... gets expanded into int&& and you can't bind an lvalue to an rvalue reference.

The reason perfect forwarding works is that when you call a function (for example) without specifying the template arguments, they get deduced to either & or && and then the references collapse (read about reference collapsing if you don't know what that is). You do explicitly specify it though, so you inhibit reference collapsing and screw it all up.

One thing you could do is give the operator() it's own list of template arguments:

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

This way you can let reference collapsing take care of it for you.

like image 57
Seth Carnegie Avatar answered Sep 24 '22 18:09

Seth Carnegie