1

The easy question is this: Can you build something that acts like a switch at compile time? The answer to this is yes. But can I make something like this optimize (or is likely to optimize) as a switch?

To explain this question: an example:

Say I have a function that takes an integer, and I want it to do something different (a behavior) for each integer input. This is easy and is a classic case of use of switch. But say that I have a template parameter pack of types that implement behaviors, and I want write the equivalent of the switch for that set of behaviors, keyed on their index in the pack. This is easy to implement, of course, with an example following.

The problem with my implementation is that the switch, at even low optimization levels, compiles into a computed jump with a small jump table. The templated solution turns into a simple set of if/then/else if/... clauses and gets compiled that way, given a high enough optimization setting.

Example can be seen here:

https://godbolt.org/g/fxQF1U

In that example, you can see the switch based implementation in the assembly at line 690. The templated version can be found at line 804. I also include the source code of my example here in case the link ever dies.

I know that a compiler can optimize as much as it wants to/can based on the "as if" clause, but is there a way to write this such that compilers are more likely to generate more optimal code?

#include <tuple>
#include <type_traits>
#include <any>
#include <string>
#include <vector>
#include <typeinfo>
#include <iostream>
#include <random>

template <typename... T>
struct Types {};

template <int I>
std::any get_type_name2p(int n) {
  return unsigned();
}

template <int I, typename T, typename... Rest>
std::any get_type_name2p(int n) {
  if (n == I) {
    return T();
  }
  return get_type_name2p<I + 1, Rest...>(n);
}

std::any get_type_name2(int n)
{
  return get_type_name2p<
    0, int, float, std::string, std::vector<int>,
    std::vector<float>, std::vector<double>, double, char>(n);
}

std::any get_type_name(int n)
{
  switch (n) {
   case 0:
    return int();
   case 1:
    return float();
   case 2:
    return std::string();
   case 3:
    return std::vector<int>();
   case 4:
    return std::vector<float>();
   case 5:
    return std::vector<double>();
   case 6:
    return double();
   case 7:
    return char();
   default:
    return unsigned();
  }
}

int main()
{
  std::random_device rd;
  std::mt19937 mt(rd());
  std::uniform_int_distribution<int> dist(1, 10);

  auto n = dist(mt);
  auto x = get_type_name(n);
  std::cout << x.type().name() << std::endl;

  auto y = get_type_name2(n);
  std::cout << y.type().name() << std::endl;

  return 0;
}
md5i
  • 3,018
  • 1
  • 18
  • 32
  • The question can be more correctly described as "a switch on types". – user202729 Jul 25 '18 at 04:38
  • Try to fill an array with lambda factory functions producing `any`. Since they're non-capturing they convert implicitly to function pointers, no need for `std::function`. Then just index that array. – Cheers and hth. - Alf Jul 25 '18 at 04:53
  • You probably want `std::variant` instead of doing this manually. – Passer By Jul 25 '18 at 06:11
  • 2
    Possible duplicate of [Variadic templates and switch statement?](https://stackoverflow.com/questions/46278997/variadic-templates-and-switch-statement) – Passer By Jul 25 '18 at 06:13
  • Turning `switch` into code might use several strategy, so in general, you have to choose the strategy for template equivalent (index, map, if chains). For your case, index seems ok btw. – Jarod42 Jul 25 '18 at 08:25
  • @PasserBy Yes, that's what I was looking for. – md5i Jul 25 '18 at 21:30

1 Answers1

1

Here is a possible solution:

std::any get_type_name(int n)
{

  if ((n<0) || (n>7)) { n = 8; }
  std::any types[9]=
  {
    int(),
    float(),
    std::string(),
    std::vector<int>(),
    std::vector<float>(),
    std::vector<double>(),
    double(),
    char(),
    unsigned()
  };

  return types[n];
}
OrenIshShalom
  • 5,974
  • 9
  • 37
  • 87