aadam
aadam

Reputation: 713

Function pointer with default behaviour

In my program I have a lot of pointers to functions provided by an external library. Some of them return value, and some of them not.

If external library did not provide function (pointer is NULL) and function should return value program should assume default value (which is known at compile time).

What I try to accomplish is to reduce amount of if statements and to wrap pointer in class with following use case:

enum E { e1, e2, e3 };
UserFunction<uint8_t(int, int), 0> callback1 { user_function_ptr }; // Calls and returns value of user_function_ptr
UserFunction<uint8_t(), 0> callback2 { nullptr }; // Returns 0
UserFunction<uint8_t(), 1> callback3 { nullptr }; // Returns 1
UserFunction<E(int), e1> callback4 { user_function_ptr2 }; // Returns enum value and takes one integer argument
UserFunction<void(int)> callback5 { user_function_ptr3 }; // No return value, one argument
UserFunction<void()> callback6 { nullptr }; // Should perform noop

What I got so far is working for functions that return value:

template <class Sign, int Def>
struct UserF;

template <class R, int Def, class... Args>
struct UserF<R(Args...), Def> {
    typedef R Signature(Args...);
    typedef typename std::add_pointer<Signature>::type SignaturePtr;

    static R Default(Args... args) {
        return (R) Def;
    }

    UserF() {
        functionToCall = Default;
    }

    UserF(SignaturePtr userFunction) {
        if (userFunction != nullptr) {
            functionToCall = userFunction;
        } else {
            functionToCall = Default;
        }
    }

    R operator() (Args... args) {
        return functionToCall(args...);
    }

private:
    SignaturePtr functionToCall;
};

The problem with code above is that it forces default value to be int. What I could do is change UserF template to something like this:

template <class R, R Def, class... Args>
struct UserF {
    // ...
};
// use case
UserF<E, e1, int> callback; // Returns E, takes int, default e1

But if it possible I would rather use

UserF<R(Args...), Default> callback; // preferred
UserF<Default, R(Args...)> callback; // if above is not possible
UserF<void(Args...)> callback; // if no return value

I would rather not use std::function since I know that I will be dealing only with pointers to functions, and not with pointers to member functions, functor objects, etc. Also boost is not allowed (C++11 is allowed).

To summarize, the question is: How to force type check on default return value.

Upvotes: 2

Views: 137

Answers (2)

tsuki
tsuki

Reputation: 907

If you can stomach a small macro, you could do this:

template <typename sig, typename T, T v >
struct UserF_;

template <class R, typename ... Args, typename T, T v >
struct UserF_< R(Args...), T, v > {
    // ...
};
#define UserF( F, Default ) UserF_< F, decltype( Default ), Default >

UserF(R(Args...), Default) callback; 

You cannot have a generic template non-type parameter without first specifying its type as a template type parameter.

Upvotes: 1

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275320

template<class Sig>
struct return_type;
template<class Sig>
using return_type_t=typename return_type<Sig>::type;
template<class R,class...Args>
struct return_type<R(Args...)>{
  using type=R;
};

template <class Sign, return_type_t<Sign> Def>
struct UserF;

is most of it. To handle void you'll want a flag trick:

struct void_flag{};
template<class T>
using flag_void=
  typename std::conditional<std::is_same<T,void>{},void_flag*,T>::type;

template <class Sign, flag_void<return_type_t<Sign>> Def=nullptr>
struct UserF;

template <class R, R Def, class... Args>
struct UserF<R(Args...), Def>{
  // body

template <class... Args>
struct UserF<void(Args...), 0>{
  // body

there will be problems with non-integral non-pointer R, as you cannot pass a double to a template. One approach would be to upgrade double to double const* for Def, and have it auto-dereference:

template <class R, R const* Def, class... Args>
struct UserF<R(Args...), Def>{
  // body

and do something like flag_void to turn T=double into T=double const*.

Upvotes: 1

Related Questions