5

Is it possible to perform a unique string to int mapping at compile time? Let's say I have a template like this for profiling:

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

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

void myFunction(){
    Profile<0> profile_me;

    /* some computations here */
}

Now I'm 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 could declare global variables to overcome this issue, but I think it would be more elegant to avoid previous declarations. A simple mapping of the form

  • ”myFunction” → 0
  • ”myFunction1” → 1
  • ”myFunctionN” → N

would be sufficient. But to this point neither using constexpr, template meta-programming nor macros I could find a way to accomplish such a mapping. Any ideas?

Community
  • 1
  • 1
Martin R.
  • 1,554
  • 17
  • 16
  • 1
    You might use pre-processor tricks, or have some (e.g.C++ header) file generated at build time. – Basile Starynkevitch Jul 01 '15 at 00:02
  • Seconded for build-time generation. – Qix - MONICA WAS MISTREATED Jul 01 '15 at 00:08
  • 1
    I don't really understand why you want to use a template here to begin with. Instead of creating a class per each function you want to profile, why not create an instance, passing the profile ID name in as a constructor argument? Or are there some other template parameters / specializations going on? – harmic Jul 01 '15 at 00:08
  • 2
    Wouldn't an `enum` be enough of a mapping? Something like: `enum { myFunction, myFunction1, ..., myFunctionN };`. And you may use it as template argument -- without even having to double-quote it :) – Rubens Jul 01 '15 at 00:09
  • if the strings are all simple as `myFunctionN`, simply strip out the `myFunction` part and convert the remaining to int – phuclv Jul 01 '15 at 01:59
  • @harmic: You mean like in the solution of Escualo? This is not bad indeed, but the run-time overhead is higher. – Martin R. Jul 01 '15 at 09:22
  • @Rubens: This would be one of the additional declarations I would like to avoid. Also I don't want to pollute the global namespace. – Martin R. Jul 01 '15 at 09:23
  • @Lưu Vĩnh Phúc: The strings should be arbitrary. – Martin R. Jul 01 '15 at 09:23
  • @MartinR. didn't you say that you just need simple mappings like in the examples? – phuclv Jul 01 '15 at 10:21
  • @Lưu Vĩnh Phúc: Yes, but maybe my example was misleading. `"myFunction1"` could also be `"foo"` and `"myFunctionN"` could be `"bar"`. They don't have to be enumerated. – Martin R. Jul 01 '15 at 10:41

6 Answers6

0

In principle you can. However, I doubt any option is practical.

You can set your key type to be a constexpr value type (this excludes std::string), initializing the value type you implement is not a problem either, just throw in there a constexpr constructor from an array of chars. However, you also need to implement a constexpr map, or hash table, and a constexpr hashing function. Implementing a constexpr map is the hard part. Still doable.

TheCppZoo
  • 1,219
  • 7
  • 12
0

You could 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]);

Maybe what you want is 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 another structure and create another table.

Note: Since the tables are declared as "static const", they are assembled during compilation time.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • Thanks for your answer. But these are still the declarations I would like to avoid. Not for performance but for aesthetic reasons. – Martin R. Jul 01 '15 at 08:49
0

It is an interesting question.

It is possible to statically-initialize a std::map as follows:

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

but I get that such initialization is not what you are looking for, so I took another approach after looking at your example.

A global registry holds a mapping between function name (an std::string) and run time (an std::size_t representing the number of milliseconds).

An AutoProfiler is constructed providing the name of the function, and it will record the current time. Upon destruction (which will happen as we exit the function) it will calculate the elapsed time and record it in the global registry.

When the program ends we print the contents of the map (to do so we utilize the std::atexit function).

The code looks as follows:

#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 obtained:

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

You do not 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 pitch in my $0.02.

And notice that if you use the following definition:

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

you can record every access to each function (instead of ditching the new results once the first entry has been written, or overwriting the old results).

Escualo
  • 40,844
  • 23
  • 87
  • 135
  • Like you said, this is not exactly what I am looking for. But your answer is very interesting nevertheless. It does avoid the kind of additional declarations I was speaking of in my question. If there wasn't the map lookup overhead, it would be perfect. – Martin R. Jul 01 '15 at 09:04
  • Notice that the timings are not polluted by any map lookup. The constructor first builds the string, and only then does it record the start time (ergo string construction is not part of the timing). Similarly, the destructor first records the end time and only then (after it has been recorded) does it calculate the duration and inserts in into the map: the timings are not polluted by any map overhead. – Escualo Jul 01 '15 at 16:48
0

As @harmic has already mentioned in the comments, you should probably just pass the name to the constructor. This might also help reduce code bloat because you don't generate a new type for each function.

However, I don't want to miss the opportunity to show a dirty hack that might be useful in situations where the string cannot be passed to the constructor. If your strings have a maximum length that is known at compile-time, you can encode them into integers. In the following example, I'm only using a single integer which limits the maximum string length to 8 characters on my system. Extending the approach to multiple integers (with the splitting logic conveniently hidden by a small macro) is left as an exercise to the reader.

The code makes use of the C++14 feature to use arbitrary control structures in constexpr functions. In C++11, you'd have to write wrap as a slightly less straight-forward 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
5gon12eder
  • 24,280
  • 5
  • 45
  • 92
0

Why not just use an Enum like:

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

?

Your strings will not be loaded in runtime, so I don't understand the reason for using strings here.

hypernote
  • 48
  • 9
  • 1
    This would be one of the additional declarations I would like to avoid. Also I don't want to pollute the global namespace. – Martin R. Jul 01 '15 at 08:42
0

You could do something similar to the following. It's a bit awkward, but may do what you want a little more directly than mapping to an integer:

#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();
}
clstrfsck
  • 14,715
  • 4
  • 44
  • 59