Is it possible to perform mapping of a string to int at compile time?

Is it possible to make a unique string to display int at compile time? Let's say I have a profiling pattern like this:

template <int profilingID>
class Profile{
public:
    Profile(){ /* start timer */ }
    ~Profile(){ /* stop timer */ }
};

      

which I place at the beginning of the function calls like this:

void myFunction(){
    Profile<0> profile_me;

    /* some computations here */
}

      

Now I am trying to do something like the following which is not possible since string literals cannot be used as a template argument :

void myFunction(){
    Profile<"myFunction"> profile_me; // or PROFILE("myFunction")

    /* some computations here */
}

      

I can declare global variables to solve this problem, but I think it would be more elegant to avoid the previous declarations. Simple view display

  • "myFunction" β†’ 0
  • "myFunction1" β†’ 1
  • ...
  • "myFunctionN" β†’ N

would be enough. But until now, neither with constexpr, template meta-programming, nor macros, I could find a way to accomplish such a mapping. Any ideas?

+3


source to share


6 answers


As @harmic mentioned in the comments , you should just pass the name to the constructor. It can also help reduce code bloat because you are not generating a new type for every function.

However, I don't want to pass up the opportunity to show a dirty hack that can be useful in situations where a string cannot be passed to a constructor. If your strings are of the maximum length known at compile time, you can encode them to integers. In the following example, I only use one integer, which limits the maximum line length to 8 characters on my system. Expanding the approach to the set of integers (with the splitting logic conveniently hidden by a small macro) is left as an exercise for the reader.

The code uses a C ++ 14 feature to use arbitrary control structures in functions constexpr

. In C ++ 11, you need to write wrap

as a slightly less straightforward recursive function.

#include <climits>
#include <cstdint>
#include <cstdio>
#include <type_traits>

template <typename T = std::uintmax_t>
constexpr std::enable_if_t<std::is_integral<T>::value, T>
wrap(const char *const string) noexcept
{
  constexpr auto N = sizeof(T);
  T n {};
  std::size_t i {};
  while (string[i] && i < N)
    n = (n << CHAR_BIT) | string[i++];
  return (n << (N - i) * CHAR_BIT);
}

template <typename T>
std::enable_if_t<std::is_integral<T>::value>
unwrap(const T n, char *const buffer) noexcept
{
  constexpr auto N = sizeof(T);
  constexpr auto lastbyte = static_cast<char>(~0);
  for (std::size_t i = 0UL; i < N; ++i)
    buffer[i] = ((n >> (N - i - 1) * CHAR_BIT) & lastbyte);
  buffer[N] = '\0';
}

template <std::uintmax_t Id>
struct Profile
{
  char name[sizeof(std::uintmax_t) + 1];

  Profile()
  {
    unwrap(Id, name);
    std::printf("%-8s %s\n", "ENTER", name);
  }

  ~Profile()
  {
    std::printf("%-8s %s\n", "EXIT", name);
  }
};

      



It can be used like this:

void
function()
{
  const Profile<wrap("function")> profiler {};
}

int
main()
{
  const Profile<wrap("main")> profiler {};
  function();
}

      

Output:

ENTER    main
ENTER    function
EXIT     function
EXIT     main

      

0


source


Basically you can. However, I doubt either option is practical.



You can set the key type as the value type constexpr

(this excludes std::string

), initialization of the value type you are implementing is also not a problem, just put the constructor there constexpr

from the array of characters. However, you also need to implement map constexpr

, hash table and hashing constexpr

. Map implementation constexpr

is the tricky part. Still doable.

0


source


You can create a table:

struct Int_String_Entry
{
  unsigned int id;
  char *       text;
};

static const Int_String_Entry my_table[] =
{
  {0, "My_Function"},
  {1, "My_Function1"},
  //...
};
const unsigned int my_table_size =
    sizeof(my_table) / sizeof(my_table[0]);

      

Perhaps you want a lookup table with function pointers.

typedef void (*Function_Pointer)(void);
struct Int_vs_FP_Entry
{
  unsigned int func_id;
  Function_Point p_func;
};

static const Int_vs_FP_Entry func_table[] =
{
  { 0, My_Function},
  { 1, My_Function1},
  //...
};

      

For more completion, you can combine all three attributes into a different structure and create another table.

Note. Since the tables are declared "static const", they are collected at compile time.

0


source


This is an interesting question.

You can statically initialize std :: map like this:

static const std::map<int, int> my_map {{1, 2}, {3, 4}, {5, 6}};

      

but I understand that initialization like this is not what you are looking for, so I took a different approach by looking at your example.

The global registry contains a mapping between the function name ( std::string

) and the runtime (a std::size_t

representing the number of milliseconds).

An An is created AutoProfiler

containing the name of the function and it will record the current time. Once destroyed (which will happen when the function exits), it will calculate the elapsed time and write it in the global registry.

When the program ends, we print the contents of the map (for this we use a function std::atexit

).

The code looks like this:

#include <cstdlib>
#include <iostream>
#include <map>
#include <chrono>
#include <cmath>

using ProfileMapping = std::map<std::string, std::size_t>;

ProfileMapping& Map() {
  static ProfileMapping map;
  return map;
}

void show_profiles() {
  for(const auto & pair : Map()) {
    std::cout << pair.first << " : " << pair.second << std::endl;
  }
}

class AutoProfiler {
 public:
  AutoProfiler(std::string name)
      : m_name(std::move(name)),
        m_beg(std::chrono::high_resolution_clock::now()) { }
  ~AutoProfiler() {
    auto end = std::chrono::high_resolution_clock::now();
    auto dur = std::chrono::duration_cast<std::chrono::milliseconds>(end - m_beg);
    Map().emplace(m_name, dur.count());
  }
 private:
  std::string m_name;
  std::chrono::time_point<std::chrono::high_resolution_clock> m_beg;
};

void foo() {
  AutoProfiler ap("foo");
  long double x {1};
  for(std::size_t k = 0; k < 1000000; ++k) {
    x += std::sqrt(k);
  }
}

void bar() {
  AutoProfiler ap("bar");
  long double x {1};
  for(std::size_t k = 0; k < 10000; ++k) {
    x += std::sqrt(k);
  }
}

void baz() {
  AutoProfiler ap("baz");
  long double x {1};
  for(std::size_t k = 0; k < 100000000; ++k) {
    x += std::sqrt(k);
  }
}

int main() {
  std::atexit(show_profiles);

  foo();
  bar();
  baz();

}

      

I compiled it as:

$ g++ AutoProfile.cpp -std=c++14 -Wall -Wextra

      

and got:

$ ./a.out
bar : 0
baz : 738
foo : 7

      

You don't need -std=c++14

, but you will need at least -std=c++11

.

I realize this is not what you are looking for, but I liked your question and decided to file in my $ 0.02.

And note that if you use the following definition:

using ProfileMapping = std::multi_map<std::string, std::size_t>;

      

you can record each access to each function (instead of recording new results after recording the first record, or overwriting old results).

0


source


Why not just use Enum like:

enum ProfileID{myFunction = 0,myFunction1 = 1, myFunction2 = 2 };

      

?

Your strings won't load at runtime, so I don't understand the reason for using strings here.

0


source


You can do something similar to the following. It's a little awkward, but it might do what you want a little more than integer matching:

#include <iostream>

template <const char *name>
class Profile{
public:
    Profile() {
        std::cout << "start: " << name << std::endl;
    }
    ~Profile() {
        std::cout << "stop: " << name << std::endl;
    }
};


constexpr const char myFunction1Name[] = "myFunction1";

void myFunction1(){
    Profile<myFunction1Name> profile_me;

    /* some computations here */
}

int main()
{
    myFunction1();
}

      

0


source







All Articles