1

For example say I have two simple functions:

void a(int x)
{
    //do something with x
}

void b(int x, float y)
{
    // do something with x and y
}

I wish to have a single function with a variable amount of args that can call both of the above, based on a flag:

template<typename... Args>
void varArgs(bool a_or_b, Args... args)
{
    if (a_or_b)
        a(args...);
    else
        b(args...);
}

The flag would tell us whether we want to use the first or the second function, however as templates are instantiated at compile time this won't work. I've read about constexpr if however I can only use c++14 so i'm wondering if there's an alternative?

EDIT: The bool can be a compile time constant, rather than a run time parameter.

max66
  • 65,235
  • 10
  • 71
  • 111
nitronoid
  • 1,459
  • 11
  • 26
  • You can't do what you want without heavy type erasure, because you are passing the boolean at runtime (and `constexpr if` will not work with that boolean, in general). If you pass the boolean as a template parameter `template void varArgs(Args... args)` then you can do it easily with tag dispatch. – Nir Friedman Jan 14 '18 at 17:43
  • @NirFriedman Just discovered that the bool can be a compile time value, would you mind posting an answer showing me how i could utilise this? – nitronoid Jan 14 '18 at 17:46
  • Did you consider "int main(int argc, char* argv[])" ... a single function with different amount of arguments. In C++, along the same lines, I have created "size_t grep (const std::string fileName, const std::string patterns, std::ostream& an_ostream = std::cout). String parameters, anyone? – 2785528 Jan 14 '18 at 17:55
  • @DOUGLASO.MOEN That's what i meant by heavier type erasure. If you want to go that route, you are better off simply passing around e.g. `std::vector`, or better, `unordered_map`, at least that way you don't have to convert all arguments into strings and back. – Nir Friedman Jan 14 '18 at 18:04

3 Answers3

5

Anything you can do with constexpr if, you can do with tag dispatch. It looks like this:

void a(int x)
{
    //do something with x
}

void b(int x, float y)
{
    // do something with x and y
}

template <typename ... Args>
void callImpl(std::true_type, Args && ... args) {
    a(std::forward<Args>(args)...);
};

template <typename ... Args>
void callImpl(std::false_type, Args && ... args) {
    b(std::forward<Args>(args)...);
};

template<bool callA, typename... Args>
void varArgs(Args&&... args)
{
    callImpl(std::integral_constant<bool, callA>{}, std::forward<Args>(args)...);
}

int main() {
    varArgs<true>(0);
    varArgs<false>(0, 0.0);
}

The idea here is that the call from varArgs to callImpl will be dispatched differently based on the value of the boolean. For this to work, we need to lift the boolean into a different type, which is why I said the boolean needed to be a template parameter and not a value. Live example: http://coliru.stacked-crooked.com/a/6c53bf7af87cdacc

Nir Friedman
  • 17,108
  • 2
  • 44
  • 72
4

Function Overloading

If you simply want to call parameter packs based on the signature of the passed arguments, you may do it with just a different overloads for the same function:

void a(int x)
{
    //do something with x
}

void a(int x, float y)
{
    // do something with x and y
}

template<typename... Args>
void dispatcher(Args... args)
{
    a(args...);
}

Tag Dispatch

If you would like to select the function based on the value of the boolean, and you will always have 2 arguments (1 int, 1 float), you may do it with a template boolean and tag dispatch:

#include <type_traits>

void a(int x)
{
    //do something with x
}

void b(int x, float y)
{
    // do something with x and y
}

template<bool B, typename... Args>
void disp_impl(std::true_type, Args... args)
{
    a(args...);
}

template<bool B, typename... Args>
void disp_impl(std::false_type, Args... args)
{
    b(args...);
}

template<bool B, typename... Args>
void dispatcher(Args... args)
{
    using type = std::integer_constant<bool, B>;
    a(type{}, args...);
}

Runtime

If you need run-time selection, you must do it the old-fashion way. Remember that all function signatures must be valid, since the branch evaluation is not known at compile-time. Therefore, this is only useful if you are passing the same arguments to all versions of the function, and the boolean value is not known at compile-time. This relies on a helper for get described here.

void a(int x)
{
    //do something with x
}

void b(int x, float y)
{
    // do something with x and y
}

template<typename... Args>
void dispatcher(bool a_or_b, Args... args)
{
    if (a_or_b)
        a(get<0>(args...));
    else
        b(args...);
}
Alex Huszagh
  • 13,272
  • 3
  • 39
  • 67
  • Would the final runtime example work for a call such as: `dispatcher(true, 1);`. It looks like it would fail to compile if there were only two args. – nitronoid Jan 14 '18 at 17:59
  • Your runtime solution still doesn't really work; if you do `dispatcher(true, 1);`, it will fail to compile instead of calling `a`. I'd probably just say that selecting this at runtime is more involved. – Nir Friedman Jan 14 '18 at 17:59
  • 1
    @nitronoid This is why the comments section needs a read-write mutex ;-) – Nir Friedman Jan 14 '18 at 18:00
  • @NirFriedman, this is assuming two arguments are **always** passed, which I clarify in the explanation. It's true, it would fail to compile. This assumes you are always calling with `dispatcher(true, 1, 2.);` or the like. – Alex Huszagh Jan 14 '18 at 18:16
  • @AlexanderHuszagh "I wish to have a single function with a variable amount of args that can call both of the above, based on a flag:". Your runtime implementation is not what that is. In fact, the usage of variadic templates is pointless, you could just take int, float, directly, and simply not pass the second argumen to a, which is far simpler, and exactly equivalent to what's happening here (though, of course, it makes it clear that it's providing very little value). – Nir Friedman Jan 14 '18 at 21:12
  • @nir Friedman, I understood the question and I understand it fails to fully fulfill the use-case, but if you have a parameter pack that you know how to resolve at compile time based on the number of arguments, there's no reason for tag dispatching or run-time selection. I believe my answer makes it clear that it's only when 1 or 2 don't work. I can edit it to make it more clear. – Alex Huszagh Jan 14 '18 at 21:40
2

Not a general solution, unfortunately, but a solution tailored to your particular case.

What about a couple of lambda wrappers with a default value for the second value in the false case?

template<typename... Args>
void varArgs (bool a_or_b, Args... args)
 {
   if ( a_or_b )
      [](int x, ...){ a(x); }(args...);
   else
      [](int x, float y = 0.0f, ...){ b(x, y); }(args...);
 }

This should works also when the value of a_or_b isn't known compile-time.

If you can modify the varArgs() signature, you can use the second-argument-with-default-value trick directly and avoid the lambda functions

void varArgs (bool a_or_b, int x, float y = 0.0f, ...)
 {
   if ( a_or_b )
      a(x);
   else
      b(x, y);
 }
max66
  • 65,235
  • 10
  • 71
  • 111
  • It's what I offered, except not completely broken. Have an up-vote. – StoryTeller - Unslander Monica Jan 14 '18 at 18:05
  • @StoryTeller - strange: usually I try do develop a broken code and you write the correct one. But thinking a little... maybe this version is overcomplicated... the second variable with default value trick can be used directly in `varArgs()` signature... I must read again the question – max66 Jan 14 '18 at 18:11