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?
source to share
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
source to share
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.
source to share
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.
source to share
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).
source to share
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();
}
source to share