简体   繁体   中英

C++11 - What is wrong with this use of decltype function pointer?

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?

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 (ie 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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM