4

The base class is :

#include <memory>

namespace cb{

template< typename R, typename ... Args >
class CallbackBase
{
public:
    typedef std::shared_ptr< CallbackBase< R, Args... > >
            CallbackPtr;

    virtual ~CallbackBase()
    {
    }
    virtual R Call(  Args ... args) = 0;
};
} // namespace cb

Derived class is this :

namespace cb{
template< typename R, typename ... Args >
class FunctionCallback : public CallbackBase< R, Args... >
{
public:
    typedef R (*funccb)(Args...);

    FunctionCallback( funccb cb_ ) : 
        CallbackBase< R, Args... >(),
        cb( cb_ )
    {
    }
    virtual ~FunctionCallback()
    {
    }
    virtual R Call(Args... args)
    {
      return cb( args... );
    }
private:
  funccb cb;
};
} // namespace cb

Function to create :

namespace cb{
template < typename R, typename ...Args >
typename CallbackBase< R, Args... >::CallbackBasePtr
    MakeCallback( typename FunctionCallback< R, Args... >::funccb cb )
{
    typename CallbackBase< R, Args... >::CallbackBasePtr
        p( new FunctionCallback< R, Args... >( cb )
);
    return p;
}
} // namespace cb

And the example :

bool Foo_1args( const int & t)
{
    return true;
}
int main()
{
    auto cbObj = cb::MakeCallback( & Foo_1args );
}

I keep getting this error :

error: no matching function for call to ‘MakeCallback(bool (*)(const int&))’
error: unable to deduce ‘auto’ from ‘<expression error>’

I tried to change it, but I couldn't figure out how to fix.

So, what is wrong? And how to fix this example?

Xeo
  • 129,499
  • 52
  • 291
  • 397
BЈовић
  • 62,405
  • 41
  • 173
  • 273

4 Answers4

7

The problem might make sense with a simpler example. Try identifying the problem here:

template <typename T>
struct id { typedef T type; };

template <typename T>
void foo(typename id<T>::type x);

foo(5); // error

The problem is that the compiler cannot deduce what T should be; it's not directly used anywhere. You'd have to explicitly provide it: foo<int>(5), or let it deduce it in some other way:

template <typename T>
void foo(typename id<T>::type x, T y);

foo(5, 7); // okay, T is int because 7 is int

This makes sense: how could the compiler figure out which T's, supplied to id, result in id<T>::type matching? There could be specializations, and the entire thing would be costly anyway, if possible.


Likewise, there's nothing the compiler has available to deduce R and Args. Instead, you should do this:

template < typename R, typename ...Args >
typename CallbackBase< R, Args... >::CallbackBasePtr
    MakeCallback( R cb(Args...) )
{
    typename CallbackBase< R, Args... >::CallbackBasePtr
        p( new FunctionCallback< R, Args... >( cb ));

    return p;
}

Finally, you have other minor issues that need fixing, which Xeo has outlined.

Community
  • 1
  • 1
GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • 2
    Should be with `typename` and `R (*cb)(Args...)`. :) – Xeo Apr 13 '11 at 23:16
  • 1
    @Xeo: Yes on the `typename`, thanks. But either syntax is fine for functions, similar to how `int i[]` becomes `int* i`. I find not putting those three characters is clearer. – GManNickG Apr 13 '11 at 23:18
  • 2
    Ah, right.. forgot the automatic conversion of functions to pointer-to-functions, you're right. But there's still a problem with that code, the return type should be `CallbackPtr` not `CallbackBasePtr`, as that typedef doesn't exist (which was part of the actual problem, as SFINAE kicked in and took that function out of the overload set). – Xeo Apr 13 '11 at 23:20
  • Your simpler example is not the same. For function pointer, the template parameters can be deduced. – BЈовић Apr 14 '11 at 07:24
  • by the way, How is your MakeCallback function different from mine? Mine is using a typedef – BЈовић Apr 14 '11 at 07:25
  • 1
    @VJo: GMan is right, the args is non-deducible. The main reason is the `::`. The `::` behavior like a "deducing-firewall", type deducing can not pass through it. – yoco Apr 14 '11 at 17:59
  • @Xeo: Thanks for babysitting my answer while I was gone. :) I feel lazy, so I just added a link to your answer as an addendum. – GManNickG Apr 14 '11 at 19:07
  • 1
    @VJo: I can only recommend [this video](http://channel9.msdn.com/Shows/Going+Deep/C9-Lectures-Stephan-T-Lavavej-Standard-Template-Library-STL-10-of-10) to you, it explains SFINAE quite nicely and tells you, why the argument is non-deducible. In general, I can recommend the whole series. – Xeo Apr 15 '11 at 07:47
4

Fixed some type-o's and specialized MakeCallback to accept function pointers. As GMan said, your template arguments to MakeCallback are in a non-deducible context.

#include <memory>

template< typename R, typename ... Args >
class CallbackBase
{
public:
    typedef std::shared_ptr< CallbackBase< R, Args... > >
            CallbackPtr;

    virtual ~CallbackBase()
    {
    }
    virtual R Call(  Args ... args) = 0;
};

template< typename R, typename ... Args >
class FunctionCallback : public CallbackBase< R, Args... >
{
public:
    typedef R (*funccb)(Args...);

    FunctionCallback( funccb  cb_ ) : 
        CallbackBase< R, Args... >(),
        cb( cb_ )
    {
    }
    virtual ~FunctionCallback()
    {
    }
    virtual R Call(Args... args)
    {
      return cb( args... );
    }
private:
  funccb cb;
};

template < typename R, typename ...Args >
typename CallbackBase< R, Args... >::CallbackPtr
    MakeCallback( R (*cb)(Args...)  )
{
    typename CallbackBase< R, Args... >::CallbackPtr
        p( new FunctionCallback< R, Args... >( cb )
);
    return p;
}

bool Foo_1args( const int & t)
{
    return true;
}
int main()
{
    auto cbObj = MakeCallback( & Foo_1args );
}

Update:

The C++ standard defines non-deduced context in 14.8.2.5 [temp.deduct.type], paragraphs 5 - 6. There is a bulleted list there which I won't claim to fully understand. The marker for me is:

Any time you see "::" after your template parameter, that template parameter is in a non-deduced context, meaning it must be explicitly specified at the call site.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
  • 1
    Might want to point out what exactly was wrong: `FunctionCallback`'s ctor wanted a `funccb *` pointer, while `funccb` is already a pointer, so it demanded a pointer-to-pointer. Second thing was the return type of `MakeCallback`, it was `CallbackBasePtr` which obviously doesn't exist, it is `CallbackPtr`. – Xeo Apr 13 '11 at 23:21
  • The same question as for GMan : How is the declaration of MakeCallback different from mine? Mine is using the same typedef. – BЈовић Apr 14 '11 at 07:26
  • I've attempted an informal definition of non-deduced context. – Howard Hinnant Apr 14 '11 at 13:22
4

To recollect what I mentioned in the comments of the other answers:

  • First, as @GMan says, your argument of MakeCallback was non-deducible.
  • Second, your return type of MakeCallback was wrong. It should be CallbackPtr, as a CallbackBasePtr typedef doesn't exist. This lead to SFINAE kicking in and not considering your function as a possible function to call even when the argument was fixed.
  • Third, your FunctionCallback constructor wanted a funccb* pointer, while funccb already is a (function-)pointer, so you would have to pass a pointer-to-function-pointer, eg. new FunctionCallback(&cb)
Community
  • 1
  • 1
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • Second and third, I understand (stupid copy&paste), but can you elaborate a bit more on the first. Why is the argument not deducible? The type should be the same, no? – BЈовић Apr 14 '11 at 07:28
4

It's better to use <functional> than to reinvent it… It's also better to copy directly from your compiler's implementation.

Generally, using fewer template parameters is a good thing, too.

But, it's always tempting to solve these problems… so, knowing what I do, but not looking directly at that right now, here is how I'd handle it.

The code to simply Call a functor will not be specialized for the different kinds of functors, so it should be in the general template case.

To make minor adjustments to the general template, a traits class serves best.

template< typename F, typename = void >
struct callback_traits {
    typedef F const &local_type; // fallback case: only keep a reference
};

template< typename F >
struct callback_traits< F,
    typename std::enable_if< // GCC 4.4 missing is_copy_constructible:
        std::is_constructible< F, F const& >::value
    >::type > {
    typedef F local_type; // better to keep a copy as this is a callback
};

template< typename F >
struct Callback {
    typedef typename callback_traits< F >::local_type local_type;
    local_type fn;

    Callback( local_type const &fn_in ) : fn( fn_in ) {}
    template< typename ... Args >
    typename std::result_of< local_type( Args ... ) >::type
    Call( Args ... a )
        { return fn( a ... ); }
};
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • Added a missing (I think) `type` after `result_of`, not so sure of myself, so I would appreciate if you cross-checked. – Matthieu M. Apr 14 '11 at 06:22
  • @Matthieu: Yes, thanks! I didn't test at all before, but with that fix it works: http://ideone.com/KkX9C - I also added support for calling `printf`. – Potatoswatter Apr 14 '11 at 07:22
  • I know I could have used `functional`, but I wanted to see what's all hype about variadic templates :) By the way, I like your solution, but how would you extend it for pointer to member method, and pointer to constant member method? – BЈовић Apr 14 '11 at 07:31
  • @VJo: Well, in `functional` they have a lot of separate specializations… see update, I refactored this to differentiate the key cases so the legwork should be reduced. New demo: http://ideone.com/IQghG – Potatoswatter Apr 14 '11 at 08:03