1

This question might provide an answer to another question I posted recently but I feel that the two questions are sufficiently different (this being more general). Also I do realize there are a few questions out there asking for something similar, e.g.

but I could not find anything on how I could implement this dispatching using a function.

Setup:

#include <vector>
#include <iostream>

template <class T>
size_t length(size_t len) {
  std::vector<T> vec(len, 100);
  return vec.size();
}

size_t length(size_t type, size_t len) {
  switch(type) {
    case 0: return length<int>(len);
    case 1: return length<double>(len);
    default: throw std::runtime_error("error");
  }
}

int main () {

  std::cout << "length: " << length(0, 4) << "\n";
  std::cout << "length: " << length(1, 5) << "\n";

  return 0;
}

I would like to write a function dispatch(type, fun, ...) that implements this switch like

auto dispatch(type, fun, ...) -> decltype(fun<int>(...)) {
  switch(type) {
    case 0: return fun<int>(...);
    case 1: return fun<double>(...);
    default: throw std::runtime_error("error");
  }
}

So far, I was able to come up with two ways of solving this:

Functor approach:

template <template<typename> class Func, typename ...Ar>
auto dispatch_type(size_t type, Ar&&... rg) ->
    decltype(Func<int>()(std::forward<Ar>(rg)...)) {
  switch(type) {
    case 0: return Func<int>()(std::forward<Ar>(rg)...);
    case 1: return Func<double>()(std::forward<Ar>(rg)...);
    default: throw std::runtime_error("error");
  }
}

template <class T>
struct Length {
  size_t operator()(size_t len) {
    std::vector<T> vec(len, 100);
    return vec.size();
  }
};

size_t length(size_t type, size_t len) {
  return dispatch_type<Length>(type, len);
}

Using boost::mp11:

#include <boost/mp11/list.hpp>
#include <boost/mp11/algorithm.hpp>

namespace mp11 = boost::mp11;

using num_types = mp11::mp_list<int, double>;
template <size_t i>
using num_type = mp11::mp_at_c<num_types, i>

template<class F>
inline constexpr
    decltype(std::declval<F>()(std::declval<mp11::mp_size_t<0>>()))
    with_type(std::size_t i, F && f) {

  return mp11::mp_with_index< mp11::mp_size<num_types> >(i,
      std::forward<F>(f));
}

size_t length(size_t i, size_t len) {
  return with_type(i, [&](auto I) {
    std::vector< num_type<I> > vec(len, 100);
    return vec.size();
  });
}

I am surprised this is so hard to achieve. Conceptually my problem is simple: Given a function template, make sure there exists explicit instantiations for a set of types (known at compile time) and dispatch the appropriate one at run time based on a switch.

What other options in addition to the two proposed ones exists? For my application I'm restricted to C++11, but within the context of this question, < C++17 is fine.

nbenn
  • 591
  • 4
  • 12
  • Not clear what the problem is you are trying to solve with your code. That `length` example can can just return `len` and that is it. – Maxim Egorushkin Aug 05 '19 at 09:32
  • Consider using std::any. – Michael Chourdakis Aug 05 '19 at 09:33
  • Do you actually just need to instantiate a template template argument for each element in a typelist? Is the return type always the same, or are you trying to allow that to vary? – Useless Aug 05 '19 at 09:38
  • @Useless the return type of a given function template is the same for all specializations, otherwise this becomes impossible, no? – nbenn Aug 05 '19 at 09:43
  • OK, so you don't need the `auto` return type that was confusing the issue. – Useless Aug 05 '19 at 09:45
  • @MaximEgorushkin assume there are many function templates like `length()` in my example. I don't want to implement the switch for each of them, instead I would write a function that handles the dispatch. Does this help? – nbenn Aug 05 '19 at 09:45
  • @nbenn It doesn't help, no. What is the high level problem? – Maxim Egorushkin Aug 05 '19 at 09:46
  • @Useless I'm sorry: within a given function template, return type stays the same, but for another function template, the return type might be different from the first. Does that make sense? – nbenn Aug 05 '19 at 09:46
  • @MaximEgorushkin the actual problem I'm facing is described in [the linked question](https://stackoverflow.com/questions/57296033/how-to-deal-with-an-rcppxptr-that-may-have-one-of-several-types) – nbenn Aug 05 '19 at 09:50
  • Question title is misleading, you never try to choose template arguments at runtime. It is also not clear why would you write "I am surprised this is so hard to achieve." - the code you wrote is basically the same as the function you were trying to implement initially. It can certainly be implemented a bit shorter by getting rid of the switch, but i don't think it worth the effort in this case. – user7860670 Aug 05 '19 at 09:50
  • @VTT I'm happy to change the title. Do you have a suggestion? The switch statement for my application spans ~10 cases, so I'd like to not repeat that for every single `length()` type function (of which I'm looking at ~50). – nbenn Aug 05 '19 at 09:56

2 Answers2

1

You can do this by simply moving your types into a typelist, and using the old-style recursion

#include <stdexcept>

// terminating case to avoid if-constexpr
template <template<class> class F,
          typename Ret>
Ret dispatch(int)
{
    // it doesn't matter what the argument is, we're out of types ...
    throw std::runtime_error("error");
}

// main recursive case
template <template<class> class F,
          typename Ret,
          typename Arg,
          typename... Args>
Ret dispatch(int index)
{
    if(index == 0)
    {
        return F<Arg>::f();
    }
    else
    {
        return dispatch<F, Ret, Args...>(index-1);
    }
}

template <typename T> struct foo;

template <> struct foo<int> { static int f(){return 1;} };
template <> struct foo<char> { static int f(){return 2;} };

and call it like

int main(void)
{
    return dispatch<foo, int, int, char>(1);
    //                         ^      ^
    //                        type_0, type_1, ...
}

You can make it nicer to read by wrapping the argument typelist up into a tuple and passing it as an argument if you want (we'll just deduce the typelist from the argument anyway, but it separates it from the return type param in the calling code).

We could also deduce the return type from F in a wrapper function, again to clean up the call site a bit:

template <template<class> class F,
          typename Arg,
          typename... Args>
auto clean_dispatch(int index, std::tuple<Arg, Args...> const &)
{
    using Ret = decltype(F<Arg>::f());
    return dispatch<F, Ret, Arg, Args...>(index);
}


int main(void)
{
    using MyTypes = std::tuple<int, char>;
    return clean_dispatch<foo>(1, MyTypes{});
}
Useless
  • 64,155
  • 6
  • 88
  • 132
  • Thanks for your suggestion. Just to clarify, due to the `if constexpr` you need in the recursion, this approach is limited to C++17 and there is no way of re-structuring that to work with C++11. Is that correct? – nbenn Aug 05 '19 at 10:12
  • You can just provide an overload for the empty argument set - hang on, I didn't notice the C++11 tag ... – Useless Aug 05 '19 at 10:14
  • One more question: I have been trying to figure out how to have functions such as `foo::f()` with variable arguments (akin to the functor approach example in my question). [What I have so far](https://gist.github.com/nbenn/bae85857e9be915e4784e53c37f04605) unfortunately does not compile. I'm sorry, I forgot to explicitly state this in my question. Is it possible to do this with your solution? – nbenn Aug 05 '19 at 15:20
  • If you can stretch to C++17, you can just use [`std::apply`](https://en.cppreference.com/w/cpp/utility/apply) and pass a second tuple with the function arguments. The tuple has to work for all overloads/instantiations of `f()` though - it can be _variadic_ but not _variable_. – Useless Aug 05 '19 at 16:21
  • If you really need to stick to C++11, it's harder - you'd really need a wrapper for each possible number of arguments AFAICS, which would be tedious. – Useless Aug 05 '19 at 16:23
0

Thanks to the answer of @Useless, I think I have a working c++11 solution using boost::mp11:

#include <vector>
#include <iostream>
#include <stdexcept>

#include <boost/mp11/list.hpp>
#include <boost/mp11/integral.hpp>
#include <boost/mp11/algorithm.hpp>

namespace mp11 = boost::mp11;

using num_types = mp11::mp_list<int, double>;

template <typename T, std::size_t N> struct dispatch_impl
{
  template <std::size_t K, template<typename> class Fn, typename ...Ar>
  static auto call(std::size_t i, Ar&&... rg) ->
      decltype(Fn<mp11::mp_at_c<T, 0>>()(std::forward<Ar>(rg)...))
  {
    if (i == 0)
    {
      return Fn<mp11::mp_at_c<T, K>>()(std::forward<Ar>(rg)...);
    }
    else
    {
      return dispatch_impl<T, N - 1>::template call<K + 1, Fn>(i - 1,
          std::forward<Ar>(rg)...);
    }
  }
};

template <typename T> struct dispatch_impl<T, 1>
{
  template <std::size_t K, template<typename> class Fn, typename ...Ar>
  static auto call(std::size_t i, Ar&&... rg) ->
      decltype(Fn<mp11::mp_at_c<T, 0>>()(std::forward<Ar>(rg)...))
  {
    if (i == 0)
    {
      return Fn<mp11::mp_at_c<T, K>>()(std::forward<Ar>(rg)...);
    }
    else
    {
      throw std::runtime_error("error");
    }
  }
};

template <template<typename> class Fn, typename ...Ar>
auto dispatch_type(size_t type, Ar&&... rg) ->
    decltype(Fn<mp11::mp_at_c<num_types, 0>>()(std::forward<Ar>(rg)...))
{
  using N = mp11::mp_size<num_types>;
  return dispatch_impl<num_types, std::size_t{N::value}>::template call<0,
      Fn>(type, std::forward<Ar>(rg)...);
}

template <class T>
struct Length
{
  size_t operator()(size_t len)
  {
    std::vector<T> vec(len, 100);
    return vec.size();
  }
};

template <>
struct Length <int>
{
  size_t operator()(size_t len)
  {
    std::vector<int> vec(len + 1, 100);
    return vec.size();
  }
};

template <>
struct Length <double>
{
  size_t operator()(size_t len)
  {
    std::vector<double> vec(len + 3, 100);
    return vec.size();
  }
};

int main ()
{
  std::cout << "length: " << dispatch_type<Length>(0, 1) << "\n";
  std::cout << "length: " << dispatch_type<Length>(1, 1) << "\n";

  return 0;
}

So far this checks all my boxes. If anyone has be better way of solving my problem, or spots potential issues with mine, I'd love to hear about it.

nbenn
  • 591
  • 4
  • 12
  • Due to the run time complexity to a given input is O(n), it may not an optimal way if the give type list is very long. As the mapping from index to the template function is static in compile time, we can make it to be more efficiently in running time by using compile time one-to-one mapping. The std::map is not constexpr, we can use a constexpr map here: https://github.com/serge-sans-paille/frozen, or copy the concept of compile time perfect hash function. – qduyang Dec 12 '19 at 07:31