28

I have a few functions defined like this:

ParentClass*    fun1();
ParentClass*    fun2();
ParentClass*    fun3(bool inp=false);
ChildClass*     fun4();
ChildClass*     fun5(int a=1, int b=3);

I would like to put them into an array of some kind as follows:

void* (*arr[5])() = {
    (void* (*)())fun1,
    (void* (*)())fun2,
    (void* (*)())fun3,
    (void* (*)())fun4,
    (void* (*)())fun5
}

Now I would like to use this array of functions simply as

for(int i=0; i<5; i++)
    someFunction(arr[i]());

Now I realize here that the issue is void* (*arr[5])(), but given that I only want to use the functions without supplying an argument, I would like all of these to be part of the same array.

These are very C-style ways to do it, though. Is there a better way to do it using Templates in C++?

5
  • Why do you want to discard the type from the return? does ChildClass not inherit ParentClass? Commented Dec 5, 2018 at 9:47
  • When I use it, I am actually casting it to ParentClass. I could make this a ParentClass* instead of void*, but that way I would have to type cast each entry into the array. I instead currently choose to cast it after I retrieve it from the array. Commented Dec 5, 2018 at 10:00
  • I miscommunicated through the question that I didn't want to use. I do want to use it. Modified the question accordingly. Commented Dec 5, 2018 at 10:32
  • In your first snippet you have declared fun4 twice, is that a typo? Commented Dec 5, 2018 at 13:48
  • Yup. Corrected. Commented Dec 5, 2018 at 17:16

5 Answers 5

42

C-style or not, what you have is straight undefined behaviour. Use lambdas:

void (*arr[5])() = {
    [] { fun1(); },
    [] { fun2(); },
    [] { fun3(); },
    [] { fun4(); },
    [] { fun5(); }
};

These are okay because they perform the call through the function's correct type, and are themselves convertible to void (*)().

Forwarding the returned value stays simple enough, since the lambda provides a context for the conversion. In your case, since ChildClass supposedly inherits from ParentClass, an implicit conversion is enough:

ParentClass *(*arr[5])() = {
    []() -> ParentClass * { return fun1(); },
    []() -> ParentClass * { return fun2(); },
    []() -> ParentClass * { return fun3(); },
    []() -> ParentClass * { return fun4(); },
    []() -> ParentClass * { return fun5(); }
};
Sign up to request clarification or add additional context in comments.

6 Comments

Does this make the return type of the functions void?
@vc669 Yes, since you don't use it anyway. However, you can also use the lambdas to reconcile the return types, which since I guess ChildClass inherits from ParentClass is just []() -> ParentClasss * { return fun1(); }.
Did not realize that my usage(or lack thereof) would make a difference. I actually do intend to use it. I have updated the question accordingly and following your comment does the trick.
@Aconcagua yes, but consistency. One could avoid repetition by using a function that takes in all of the fun*'s at once and returns the array of wrapped functions as well.
@Aconcagua Almost: it gets compiled into a single jmp. Although the function's body will be inlined into the lambda if applicable.
|
16

but given that I only want to use the functions without supplying an argument

It simply doesn't work like that. Did you ever wonder, then when you're putting function declarations in a header, why you have to write default parameters into the header and cannot place it in the definition in the implementation source file?

That's because the default parameters are in fact not "embedded" into the function, but used by the compiler to augment a function call with those parameters at a calling location, where those parameters are omitted. (EDIT: Also, as @Aconcagua so keenly observed in a comment, since default parameters are usually defined as part of a header function declaration, any change of the default values requires a full recompilation of any compilation unit that included those headers, ergo function declarations, for the change to actually take effect!)

While it's perfectly possible to do some really weird type casting madness to construct an array of function pointers like that, eventually you'll have to cast back to the original function call signature in order to not invoke undefined behavior.

If anything you'll have to bind the function pointer, together with a set of default parameters in some type that abstracts away the calling, does supply the parameters, and to the outside offers a polymorphic interface. So you'd have a std::vector<function_binder> or function_binder[] where function binder has an operator() that calls the function.

But when you're doing binding in the first place, you can bind it in an anonymous function, i.e. lambdas. At the time of lambda instantiation the default parameters are bound.

std::vector<void(*)()> fvec = {
    []{ func0(); },
    []{ func1(); },
    []{ func2(); }
};

1 Comment

Might be worth to mention that, as parameters are provided in header, if changing these, any translation units relying on need to be re-compiled to make these changes effective there as well...
10

You can use std::bind

std::function<ParentClass *(void)> arr[5] = {
    std::bind(&fun1),
    std::bind(&fun2),
    std::bind(&fun3, false),
    std::bind(&fun4),
    std::bind(&fun5, 1, 3)
};

now you can do

for(int i=0; i<5; i++)
    arr[i]();

You have to make sure every function parameter of all functions are bound.

This also works well with member functions. You just have to bind the object reference (e.g. this) as first parameter.

8 Comments

since C++11 and above there is no point to use bind since lambda is more handy and faster.
@Marek R - Depending on implementation std::bind might just return an lambda because it's return type is unspecified. It's probably just a matter of taste which one you use.
My taste says std::bind since if forces less disciplined developer to write small functions. Lambda is often overused and grows to ridicules sizes, but still it is easier to use and it is faster. std::bind creates complex templates which is annoying when analyzing a crash logs.
@Quentin - Thanks. Of course you can't have an array of auto. I corrected that.
@Detonar and now it's because std::function is way overkill for the job :p
|
4

A solution:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }


template<auto f, class R, class...Args>
struct explicit_function_caster {
  using Sig=R(Args...);
  using pSig=Sig*;
  constexpr operator pSig()const {
    return [](Args...args)->R {
      return static_cast<R>(f(std::forward<Args>(args)...));
    };
  }
};

template<auto f>
struct overload_storer_t {
  template<class R, class...Args>
  constexpr (*operator R() const)(Args...) const {
    return explicit_function_caster<f, R, Args...>{};
  }
  template<class...Args>
  auto operator()(Args&&...args)
  RETURNS( f( std::forward<Args>(args)... ) )
};
template<auto f>
overload_storer_t<f> generate_overloads={};

#define OVERLOADS_OF(...) \
  generate_overloads< \
    [](auto&&...args) \
    RETURNS( __VA_ARGS__( decltype(args)(args)... ) ) \
  >

which is a lot of boilerplate, but gets us:

ParentClass* (*arr[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};
void (*arr2[5])() = {
  OVERLOADS_OF(fun1),
  OVERLOADS_OF(fun2),
  OVERLOADS_OF(fun3),
  OVERLOADS_OF(fun4),
  OVERLOADS_OF(fun5)
};

basically generate_overloads<x> takes a constexpr callable object x and lets you cast it at compile time to a pointer to a function of any compatible signature and call it with (almost) any signature.

Meanwhile, OVERLOADS_OF converts a function name into a constexpr object that does overload resolution on that function name. I use it here because fun3 as a function pointer does not know about its default arguments, but at overload resolution time it does.

In this particular case, it is far easier to just write toy lambdas to do this work; this is just an attempt to automate writing those toy lambdas for arbitrary compatible signatures.

3 Comments

constexpr (*operator R() const)(Args...) const oh what the hell... Also, is decltype(args)(args)... a replacement for std::forward or is there additional subtlety?
@Quentin Yes. I don't have the type of args. The cast operator was there because the C++2a compilers didn't like auto based partial specialization, so I couldn't easily unpack a Sig: that is a type-deduced conversion operator to function pointer.
@Quentin Laugh, I think I converted to a const function pointer in a const method. Accidentally: the fact the function pointer is const is pointless.
3

Since you have tagged question with C++14 you should not use function pointers!

With C++14 you should prefer std::function and lambdas.

Also you should not use C style array, but only std::array and/or std::vector.

Also avoid raw pointers, use std::unique_ptr and std::shared_ptr.

So simplest and best way to solve it is:

std::array<std::function<ParentClass*()>,5> arr {
    []() { return fun1(); },
    []() { return fun2(); },
    []() { return fun3(true); },
    []() { return fun4(); },
    []() { return fun5(7, 9); }
};

Why not simple array of pointers like in @Quentin answer? He used lambdas, but he can't use lambda which binds anything (if you need to).

1 Comment

Downvoted because std::function is a heavyweight facility, and the problem at hand does not require any of the functionality it provides over plain function pointers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.