Pergunta

While trying to implement a Delegate-class using variadic templates I ran into a problem I'm unable to solve:

/// --------------------------------------
/// @thanks     God
///             Steve Reinalter
/// @author     Henri Korpela aka Helixirr
/// --------------------------------------
#include <cstdio>

template<typename>
class Delegate;

template<typename Return, typename Param, typename... ParamsOther>
class Delegate<Return (Param, ParamsOther...)>{
public:

    /// Constructors & destructors:
    Delegate(void) = default;
    Delegate(Delegate const& delegate_) = default;
    Delegate(Delegate&& delegate_) = default;

    /// Member functions:
    Delegate& bind(Return (*function_)(Param, ParamsOther...));
    template<class C>
    Delegate& bind(C& c_, Return (C::*function_)(Param, ParamsOther...));

    /// Member functions (overloaded operators, assignment):
    Delegate& operator=(Delegate const& delegate_) = default;
    Delegate& operator=(Delegate&& delegate_) = default;

    /// Member functions (overloaded operators, function call):
    inline Return operator()(Param param_, ParamsOther... params_other_) const;

private:
    /// Member data:
    Return (*_m_opFunction)(Param, ParamsOther...) = nullptr;
    void* _m_opInstance = nullptr;
    /// Static member functions:
    template<class C, Return (C::*Function)(Param, ParamsOther...)>
    static inline Return _wrap_function_member(void* instance_, Param param_, ParamsOther... params_other_);
    template<Return (*Function)(Param, ParamsOther...)>
    static inline Return _wrap_function_static(void*, Param param_, ParamsOther... params_other_);
};

/// Member functions:
template<typename Return, typename Param, typename... ParamsOther>
Delegate<Return (Param, ParamsOther...)>& Delegate<Return (Param, ParamsOther...)>::bind(Return (*function_)(Param, ParamsOther...)){
    _m_opFunction = &_wrap_function_static<decltype(function_)>;
    _m_opInstance = nullptr;
    return *this;
}
template<typename Return, typename Param, typename... ParamsOther>
template<class C>
Delegate<Return (Param, ParamsOther...)>& Delegate<Return (Param, ParamsOther...)>::bind(C& c_, Return (C::*function_)(Param, ParamsOther...)){
    _m_opFunction = &_wrap_function_member<C, decltype(function_)>;
    _m_opInstance = &c_;
    return *this;
}
/// Member functions (overloaded operators, function call):
template<typename Return, typename Param, typename... ParamsOther>
Return Delegate<Return (Param, ParamsOther...)>::operator()(Param param_, ParamsOther... params_other_) const{
    return _m_opFunction(_m_opInstance, param_, params_other_...);
}
/// Static member functions:
template<typename Return, typename Param, typename... ParamsOther>
template<class C, Return (C::*Function)(Param, ParamsOther...)>
Return Delegate<Return (Param, ParamsOther...)>::_wrap_function_member(void* instance_, Param param_, ParamsOther... params_other_){
    return (static_cast<C*>(instance_)->*Function)(param_, params_other_...);
}
template<typename Return, typename Param, typename... ParamsOther>
template<Return (*Function)(Param, ParamsOther...)>
Return Delegate<Return (Param, ParamsOther...)>::_wrap_function_static(void*, Param param_, ParamsOther... params_other_){
    return (Function)(param_, params_other_...);
}

int f(int i_){
    return i_ * 2;
}

int main(void){
    Delegate<int (int)> delegate__;
    delegate__.bind(&f);
    printf("Result: %i\n", delegate__(8));
    return 0;
}

I tried to compile this on Ideone with C++11 compiler (GCC 4.7.2), but it seems to fail:

prog.cpp: In instantiation of ‘Delegate& Delegate::bind(Return (*)(Param, ParamsOther ...)) [with Return = int; Param = int; ParamsOther = {}]’: prog.cpp:79:23: required from here prog.cpp:45:5: error: no matches converting function ‘_wrap_function_static’ to type ‘int (*)(int)’ prog.cpp:39:26: error: candidate is: template static Return Delegate::_wrap_function_static(void*, Param, ParamsOther ...) [with Return (* Function)(Param, ParamsOther ...) = Function; Return = int; Param = int; ParamsOther = {}] prog.cpp: In instantiation of ‘Return Delegate::operator()(Param, ParamsOther ...) const [with Return = int; Param = int; ParamsOther = {}]’: prog.cpp:80:40: required from here prog.cpp:59:65: error: invalid conversion from ‘void*’ to ‘int’ [-fpermissive] prog.cpp:59:65: error: too many arguments to function

From what I can understand, decltype and function pointer here

template<typename Return, typename Param, typename... ParamsOther>
Delegate<Return (Param, ParamsOther...)>& Delegate<Return (Param, ParamsOther...)>::bind(Return (*function_)(Param, ParamsOther...)){
    _m_opFunction = &_wrap_function_static<decltype(function_)>;
    _m_opInstance = nullptr;
    return *this;
}

seem to be causing the problem. Same happens when I try to bind member function to the delegate. Why is this so? What am I doing wrong? To me it seems pretty natural to get the type of the function pointer and use that type as a template argument, but for some reason, it won't work here. What is wrong with this decltype and function pointer scenario?

Foi útil?

Solução

Here's the first part of the error messages from clang++ 3.2:

temp.cpp:41:19: error: assigning to 'int (*)(int)' from incompatible type
      '<overloaded function type>'
    _m_opFunction = &_wrap_function_static<decltype(function_)>;
                  ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
temp.cpp:75:16: note: in instantiation of member function 'Delegate<int
      (int)>::bind' requested here
    delegate__.bind(&f);
               ^
temp.cpp:35:26: note: candidate function has different number of parameters
      (expected 1 but has 2)
    static inline Return _wrap_function_static(void*, Param param_,...
                         ^


temp.cpp:55:41: error: too many arguments to function call, expected 1, have 2
    return _m_opFunction(_m_opInstance, param_, params_other_...);
           ~~~~~~~~~~~~~                ^~~~~~
temp.cpp:76:38: note: in instantiation of member function 'Delegate<int
      (int)>::operator()' requested here
    printf("Result: %i\n", delegate__(8));

This occurs because of the declaration of _m_opFunction:

Return (*_m_opFunction)(Param, ParamsOther...) = nullptr;

template<Return (*Function)(Param, ParamsOther...)>
static inline Return _wrap_function_static(void*, Param param_, ParamsOther... params_other_);

That is, _wrap_function_static expects a void* then the arguments to forward to the function call whereas _m_opFunction only expects the arguments for the function call.


The Standard Library solution:

#include <iostream>
#include <functional>

int f(int i_){
    return i_ * 2;
}

struct foo
{
    int m;
    int f(int i) { return i * m; }
};

int main()
{
    std::function<int (int)> delegate__;

    delegate__ = f;
    std::cout << "Result: " << delegate__(8) << std::endl;

    foo o;
    o.m = 21;
    delegate__ = std::bind(&foo::f, std::ref(o), std::placeholders::_1);
    std::cout << "Result: " << delegate__(2) << std::endl;
}

Trying to fix your approach: Note: You cannot convert from a member function pointer to an "ordinary" function pointer (it may work using unions or copying the raw data... UB). A better approach would be to use polymorphism (i.e. virtual functions & dynamic allocation of a caller object).

#include <cstdio>

template<typename>
class Delegate;

template<typename Return, typename Param, typename... ParamsOther>
class Delegate<Return (Param, ParamsOther...)>{
public:

    /// Constructors & destructors:
    Delegate(void) = default;
    Delegate(Delegate const& delegate_) = default;
    Delegate(Delegate&& delegate_) = default;

    /// Member functions:
    Delegate& bind(Return (*function_)(Param, ParamsOther...));
    template<class C>
    Delegate& bind(C& c_, Return (C::*function_)(Param, ParamsOther...));

    /// Member functions (overloaded operators, assignment):
    Delegate& operator=(Delegate const& delegate_) = default;
    Delegate& operator=(Delegate&& delegate_) = default;

    /// Member functions (overloaded operators, function call):
    inline Return operator()(Param param_, ParamsOther... params_other_) const;

private:
    /// Member data:
    Return (*_m_opFunction)(Param, ParamsOther...) = nullptr;
    Return (Delegate::*_m_opMemFunction)(Param, ParamsOther...) = nullptr;
    void* _m_opInstance = nullptr;

    /// function wrappers:
    template<class C>
    static inline Return _wrap_member_function(Delegate const&, Param param_, ParamsOther... params_other_);
};

/// Member functions:
template<typename Return, typename Param, typename... ParamsOther>
Delegate<Return (Param, ParamsOther...)>& Delegate<Return (Param, ParamsOther...)>::bind(Return (*function_)(Param, ParamsOther...)){
    _m_opFunction = function_;
    _m_opMemFunction = nullptr;
    _m_opInstance = nullptr;
    return *this;
}
template<typename Return, typename Param, typename... ParamsOther>
template<class C>
Delegate<Return (Param, ParamsOther...)>& Delegate<Return (Param, ParamsOther...)>::bind(C& c_, Return (C::*function_)(Param, ParamsOther...)){
    _m_opFunction = reinterpret_cast<decltype(_m_opFunction)>( &_wrap_member_function<C> );
    _m_opMemFunction = reinterpret_cast<decltype(_m_opMemFunction)>( function_ );
    _m_opInstance = &c_;
    return *this;
}
/// Member functions (overloaded operators, function call):
template<typename Return, typename Param, typename... ParamsOther>
Return Delegate<Return (Param, ParamsOther...)>::operator()(Param param_, ParamsOther... params_other_) const{
    if(nullptr == _m_opMemFunction)
    {
        return _m_opFunction(param_, params_other_...);
    }else
    {
        auto f = reinterpret_cast<Return (*)(Delegate const&, Param, ParamsOther...)>( _m_opFunction );
        return f(*this, param_, params_other_...);
    }
}
/// function wrappers:
template<typename Return, typename Param, typename... ParamsOther>
template<class C>
Return Delegate<Return (Param, ParamsOther...)>::_wrap_member_function(Delegate<Return (Param, ParamsOther...)> const& instance_, Param param_, ParamsOther... params_other_){
    Return (C::*memFuncPtr)(Param, ParamsOther...) = reinterpret_cast<decltype(memFuncPtr)>( instance_._m_opMemFunction );
    return (reinterpret_cast<C*>(instance_._m_opInstance)->*memFuncPtr)(param_, params_other_...);
}

int f(int i_){
    return i_ * 2;
}

struct foo
{
    int m;
    int f(int i) { return i * m; }
};

int main(void){
    Delegate<int (int)> delegate__;
    delegate__.bind(&f);
    printf("Result: %i\n", delegate__(8));

    foo o;
    o.m = 21;
    delegate__.bind(o, &foo::f);
    printf("Resilt: %i\n", delegate__(2));
    return 0;
}

Why I said "the design is arguably flawed":

  • Standard Library already contains a "delegate" type (std::function)
    • Order of arguments is different for member functions in StdLib bind (std::bind(&A::func, instance) vs delegate__.bind(instance, &A::func))
    • StdLib bind copies the instance, you have to explicitly use std::ref (or a pointer) to pass a reference <- less error prone because it's obvious you have to keep the instance alive until the function is called
  • function call copies parameters (better: perfect forwarding)
  • Why splitting up the parameters in Param and ParamOthers?
  • incompatible to functors
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top