yoyoy
yoyoy

Reputation: 395

Calling different versions of a template function depending on run-time argument

I have several template functions whose template parameters always belong to the same enum, and would like to call the various instantiations based on the run-time value of a variable from that enum:

#include <iostream>

enum Number {One, Two};

template<Number N>
void g(int m) {
  std::cout << N << " " << m << std::endl;
}

int main() {

  auto n = Number::One;
  int m = 0;

  // I want to use this sort of construct for many different functions g
  if (n == Number::One) {
    g<Number::One>(m);
  }
  else if (n == Number::Two) {
    g<Number::Two>(m);
  }
}

Rather than write the branch every time, I would like to refactor it - here's my current attempt:

struct h {
  template<Number N>
  void operator()(int m) const {
    g<N>(m);
  }
};

template <typename F, typename... Args>
void split(F&& f, Number n, Args&&... args) {
  if (n == Number::One) {
    f.template operator()<Number::One>(args...);
  }
  else if (n == Number::Two) {
    f.template operator()<Number::Two>(args...);
  }
}

int main() {
  split(h{}, n, m);
}

Is this the cleanest way to achieve this? In particular, is it possible to rewrite split such that it will accept g directly, without me needing to wrap in struct h?

Upvotes: 3

Views: 1102

Answers (3)

HolyBlackCat
HolyBlackCat

Reputation: 96256

You have to wrap your function in something, but we can use a lambda instead of a class.

If your enum values are contiguous, use this:

(The code below relies on contiguous enum values. It allows us to use an array of function pointers for fast dispatch. If your enum is not contiguous, read below.)

#include <iostream>    
#include <utility>    

enum Number {One, Two, NumberCount};

template <Number N> void g(int m)
{
    std::cout << N << " " << m << std::endl;
}

template <typename L, std::size_t ...I>
const auto &split_impl(std::index_sequence<I...>, L lambda)
{
    static decltype(lambda(std::integral_constant<Number, One>{})) array[] =
    {lambda(std::integral_constant<Number, Number(I)>{})...};
    return array;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
    split_impl(std::make_index_sequence<NumberCount>{}, lambda)[n](std::forward<P>(p)...);
}

int main()
{
    auto wrapped_g = [](auto i){return g<i.value>;};
    split(wrapped_g, One, 42);
}

If your enum values are NOT contiguous, use this:

#include <iostream>    
#include <utility>    

enum Number {One, Two};

template <Number N> void g(int m)
{
    std::cout << N << " " << m << std::endl;
}

template <Number ...I, typename L> auto split_impl(L lambda, Number n)
{
    decltype(lambda(std::integral_constant<Number, One>{})) ret = 0;
    ((I == n ? (ret = lambda(std::integral_constant<Number, Number(I)>{}), 0) : 1) && ...);
    return ret;
}
template <typename L, typename ...P> void split(L lambda, Number n, P &&... p)
{
    split_impl<One, Two>(lambda, n)(std::forward<P>(p)...);
    //         ^~~~~~~~
    // List all your enum values here
}

int main()
{
    auto wrapped_g = [](auto i){return g<i.value>;};
    split(wrapped_g, One, 42);
}

P.S. I used std::size_t and std::index_sequence for simplicity. If you want robustness, you should use std::integral_sequence<std::underlying_type_t<MyEnum>>.

Upvotes: 4

Jens
Jens

Reputation: 9406

From the first example

#include <iostream>

enum Number {One, Two};

template<Number N>
void g(int m) {
  std::cout << N << " " << m << std::endl;
}

int main() {

  auto n = Number::One;
  int m = 0;

  // I want to use this sort of construct for many different functions g
  if (n == Number::One) {
    g<Number::One>(m);
  }
  else if (n == Number::Two) {
    g<Number::Two>(m);
  }
}

I assume that you essentially want to dispatch to the template specializations g<N> depending on a runtime value n. So essentially you want to replace hand-written if-else-cascades or a switches with some centralized dispatch logic.

The following code iterates over the enum values and calls the function g<N> matching the function parameter m. It does only work if the enum is consecutive and defines an end value for the iteration. This at least centralizes the dispatch, and if the enum is modified, the dispatch will work automatically.

 #include <functional>
#include <iostream>

enum class Number {One, Two, Three, MAX};

template<Number N>
void g(int m) {
  std::cout << static_cast<int>(N) << " " << m << std::endl;
}

template<Number N>
struct Dispatch {
    void call(Number n, int m) {
        if (N == n) {
            g<N>(m);
        } else if (N != Number::MAX) {
            Dispatch< static_cast<Number>(static_cast<int>(N)+1) > next;
            next.call(n, m);
        } 
    }
};

template<>
struct Dispatch<Number::MAX> {
    void call(Number, int) {
        throw "Ohje";
    }
};

void dispatch(Number n, int m) {
    Dispatch<Number::One> d;
    d.call(n, m);
}

int main(int argc, char* argv[]) {
    std::cout << argc << std::endl;
    dispatch(static_cast<Number>(argc-2), 42);
    return 0;
}

You can further generalize the code to use it with different functions for g. I wrapped the function call into helper structs to represent the free function g<N>. We can then pass the function to call if N and m match to `d

#include <functional>
#include <iostream>

enum class Number {One, Two, Three, MAX};

template<Number N>
void g(int m) {
  std::cout << "g" << static_cast<int>(N) << " " << m << std::endl;
}

template<Number N> struct  S {
    void operator()(int m) {
        std::cout << "S" << std::endl;
        g<N>(m);
    }
};

template<Number N> struct  G {
    void operator()(int m) {
        std::cout << "G" << std::endl;
        g<N>(m);
    }
};

template<template<Number> typename C, Number N>
struct Dispatch {
    void call(Number n, int m) {
        if (N == n) {
            C<N> c;
            c(m);
        } else if (N != Number::MAX) {
            Dispatch< C, static_cast<Number>(static_cast<int>(N)+1) > next;
            next.call(n,m);
        } 
    }
};

template<template<Number> typename C>
struct Dispatch<C, Number::MAX> {
    void call(Number, int) {
        throw "Ohje";
    }
};

template<template<Number> typename C>
void dispatch(Number n, int m) {
    Dispatch<C, Number::One> d;
    d.call(n,m);
}

int main(int argc, char* argv[]) {
    std::cout << argc << std::endl;
    dispatch<S>(static_cast<Number>(argc-2), 42);
    dispatch<G>(static_cast<Number>(argc-2), 23);
    return 0;
}

Upvotes: 2

D Drmmr
D Drmmr

Reputation: 1243

It's possible with some modifications to the way g is defined. In particular, I don't think it's possible to represent a template function in the type system, but it is possible to represent a template class/struct. So, I changed g to a struct with a static member function.

#include <iostream>

enum Number { One, Two };

template <Number N>
struct g
{
  static void call(int m)
  {
    std::cout << N << " " << m << std::endl;
  }
};

template <template<Number> class F, class R = void>
struct Wrap
{
  template <class... Args>
  static R call(Number n, Args&&... args)
  {
    switch (n) {
      case One: return F<One>::call(std::forward<Args>(args)...);
      case Two: return F<Two>::call(std::forward<Args>(args)...);
    }
  }
};

int main()
{
  auto n = Number::One;
  int m = 0;

  Wrap<g>::call(n, m);
}

Upvotes: 1

Related Questions