Building a switch at compile time

时间:2018-07-25 04:32:52

标签: c++ c++17

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;
}

1 个答案:

答案 0 :(得分:1)

这是一个可能的解决方案:

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];
}