hash template metafunction and function - c++

Any computation is possible at compile-time with C++ template metafunctions. Therefore, I was considering, if the following were possible:
void my_function(char const* string_ptr)
{
switch (hash_function(string_ptr))
{
case hash_metafunction<"yoohooo">::value:
...
break;
case hash_metafunction<"woooooo">::value:
...
break;
...
}
}
Can you give leads as to where to find code (library) for both the hash function and template metafunction. If none such library exists, can you give hints on how I might roll the template metafunction myself? I am particularly worried about the char const* parameter to the template metafunction. Maybe some preprocessor magic is possible?

How about a constexpr function? Of course implementing that hash could be a pain. You'll have something like this:
// maybe another return type
constexpr uint64_t hash_metafunction(const char* input) {
// replace some_value with the hash implementation
return some_value;
}
void my_function(char const* string_ptr)
{
switch (hash_function(string_ptr))
{
case hash_metafunction("yoohooo"):
...
break;
case hash_metafunction("woooooo"):
...
break;
...
}
}
The hash_metafunction function would be executed on compile-time.
Edit: This is a naive implementation, which basically converts the input string to a uint64_t:
constexpr uint64_t do_the_hash(const char* input, uint64_t value_so_far) {
return *input ? do_the_hash(input + 1, (value_so_far << 8) | *input) : value_so_far;
}
constexpr uint64_t hash_metafunction(const char* input) {
return do_the_hash(input, 0);
}
Live demo here.
Edit: I've implemented a compile time MD5, you can find the source code here. In order to use it, do the following:
#include <iostream>
#include "md5.h"
int main() {
constexpr auto value = ConstexprHashes::md5("constexpr rulz");
std::cout << std::hex;
for(auto v : value) {
if(((size_t)v & 0xff) < 0x10)
std::cout << '0';
std::cout << ((size_t)v & 0xff);
}
std::cout << std::endl;
}
This prints out the hash: "b8b4e2be16d2b11a5902b80f9c0fe6d6".

I have created a constexpr version of MurmurHash3 as a gist on GitHub.
int main() {
constexpr uint32_t hash = Murmur3_32("some_string_to_hash", 0xAED123FD);
assert(hash == 4291478129);
}

Related

How to deduce a return type in C++

I want to create some kind of Variant in C++. Actually I want to use templates as less as possible. The idea is to store the value in union both with the type of the variable and return the value according to the stored type.
So the test code looks like following:
#include <iostream>
#include <vector>
#include <cstring>
#include <typeinfo>
#include <typeindex>
using namespace std;
constexpr uint64_t mix(char m, uint64_t s)
{
return ((s << 7) + ~(s >> 3)) + static_cast<uint64_t>(~m);
}
constexpr uint64_t _(const char* str)
{
return (*str) ? mix(*str,_(str + 1)) : 0;
}
class Variant
{
public:
template<typename T>
Variant(T value):
m_info(typeid(value))
{
std::memcpy(&m_val, &value, sizeof(T));
}
auto toValue() ->decltype(???) // what have I use here ???
{
switch(_(m_info.name()))
{
case _("b"):
return m_val.bval;
case _("i"):
return m_val.ival;
case _("d"):
return m_val.dval;
break;
}
return 0;
}
char cval;
unsigned char ucval;
private:
union Types
{
bool bval;
int ival;
double dval;
} m_val;
std::type_index m_info;
};
Usage:
int main()
{
std::vector<Variant> arr = { 1, 2.2, true };
for(auto &v: arr)
{
cout << "value is: " << v.toValue() << endl;
}
return 0;
}
But decltype requires an expression as a parameter and that's where I'm stuck. What expression have I use here?
As per #UnholySheep's comment, what you're trying to do is have a function whose return type is deduced at runtime, which is simply not possible. The return type has to be known at compile time. So you're going to have to change your API. There are a few different options here.
This seems similar to std::variant, whose API equivalent to your toValue() looks like this:
std::get<double>(variant)
std::get<int>(variant)
std::get<bool>(variant)
This function call std::get will throw std::bad_variant_access if you try to get the value with the wrong type. You could do that here.
Another option is to extract the union { bool, int, double } type out of the Variant class so you can use it as the return type. Then it'd probably be advisable to have another function call so the caller can tell at runtime which type the union actually is. You could return an enum or just return your m_type variable for this.

Using boost mp11 to switch on runtime value efficiently(breaking when processing function is done)

I have the following code where I implement dispatching on runtime value to interpret the data in certain way(in this toy example data can be either uint8_t or short).
Code seems to work, but I am wondering if I can somehow microoptimize the code so that when I have a hit(processing function matches) processing is stopped (currently even if first element of tuple is a "handler" entire tuple is iterated over at runtime).
#include <boost/mp11/tuple.hpp>
#include <iostream>
uint8_t data[4] = {0,1,100,2};
template<int runtimeId, typename T>
struct kindToType{
static constexpr int id = runtimeId;
using type = T;
};
const auto print =[]<typename T> (const T* data){
if constexpr(std::is_same_v<short, std::remove_cvref_t<T>>){
const short* values = (const short*)data;
std::cout << values[0] << " " << values[1] << std::endl;
} else if constexpr(std::is_same_v<uint8_t, std::remove_cvref_t<T>>){
const uint8_t* values = (const uint8_t*)data;
std::cout << (int)values[0] << " " << (int)values[1]<< " " << (int)values[2] << " " << (int)values[3] << std::endl;;
}
};
static constexpr std::tuple<kindToType<10, uint8_t>, kindToType<11, short>> mappings{};
void dispatch(int kind){
boost::mp11::tuple_for_each(mappings, [kind]<typename Mapping>(const Mapping&) {
if (Mapping::id == kind)
{
print((typename Mapping::type*)data);
}
});
}
int main()
{
// no guarantee that kind is index like(e.g. for two values
// it can have values 47 and 1701)
dispatch(10);
dispatch(11);
}
Notes:
I can not/want to use std::variant.
I do not want to use std::map or std::unordered map(where value is std::function)
I know this is premature optimization(even 10 integer comparisons is cheap assuming handlers do nontrivial amount of work).
my handlers are unique, i.e. it is std::map like thing, not std::multimap like thing so it is fine to break;.
kind of id used for runtime values is not guaranteed to have values in [0, n-1].
I am fine with C++20 solution as long as it is implemented in at least 1 compiler.
The runtime performance of this heavily depends on the size of your tuple. You can make your own for_each_tuple implementation that does an early out when your function gets executed:
template<typename FuncTuple, typename Selector>
void tuple_for_each(FuncTuple const& funcTuple, Selector selector)
{
std::apply([selector](auto const& ...funcs)
{
(void)(selector(funcs) || ...);
}, funcTuple);
}
your dispatch would then look like this:
void dispatch(int kind)
{
tuple_for_each(mappings, [kind]<typename Mapping>(const Mapping&)
{
std::cout << "loop, ";
if (Mapping::id == kind)
{
print((typename Mapping::type*)data);
return true;
}
return false;
});
}
If you get rid of the template in your lambda and use auto instead this code will compile with C++17. We use operator short circuiting to our advantage so the compiler will provide an early out for us. Here is the full code.
Also, note that the cast (const short*)data is UB.

How to convert a string to a constant integer?

I have strings which represent image names like "foobar.png" etc.
As you know, switch-case in C++ does not support switching on a string.
I'm trying to work around this, by hashing the string to std::size_t, and then using that value in the switch-case statements.
For example:
//frameName is an std::string which represents foobar.png etc..
switch (shs(frameName)) { //shs is my hash func which returns std::size_t;
case shs(Pfn::fs1x1): //Problem in this line
default:{
break;
}
}
In a separate file (Pfn.hpp):
namespace Pfn{
const std::string fs1x1 = "fs1x1";
};
The problem is, that in my case statement the compiler reports that shs(Pfn::fs1x1) is not a constant expression. The exact error message is:
Case value is not a constant expression:
It would be really tedious to work out all the hash-values in advance and then hardcode them into the case statements. Do you have a suggestion on how I can somehow create the constant expressions at runtime ?
EDIT: My shs function:
static std::size_t shs(std::string string){
return Hash::shs::hs(string);
}
//...
namespace Hash{
struct shs{
public:
inline std::size_t operator()(const std::string &string)const{
return hashString(string);
}
static std::size_t hs(const std::string &string){
std::size_t seed = 0;
hash_combine(seed,string);
return seed;
}
//From Boost::hash_combine.
template <class T>
static inline void hash_combine(std::size_t& seed, const T& v)
{
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
};
};
}
shs's argument needs to be constexpr and shs itself must be constexpr as well. Chances are, you might want to provide different implementations for the compile-time version and the run-time version of the hash, due to C++11 constraints on constexpr functions.
I wrote a post on this very topic some time ago, using fnv1a as the hash algorithm. Here are the important parts:
constants:
typedef std::uint64_t hash_t;
constexpr hash_t prime = 0x100000001B3ull;
constexpr hash_t basis = 0xCBF29CE484222325ull;
Runtime hash:
hash_t hash(char const* str)
{
hash_t ret{basis};
while(*str){
ret ^= *str;
ret *= prime;
str++;
}
return ret;
}
Compile-time hash:
constexpr hash_t hash_compile_time(char const* str, hash_t last_value = basis)
{
return *str ? hash_compile_time(str+1, (*str ^ last_value) * prime) : last_value;
}
user defined string literal:
constexpr unsigned long long operator "" _hash(char const* p, size_t)
{
return hash_compile_time(p);
}
and usage:
switch(fnv1a_64::hash(str)){
case "first"_hash:
cout << "1st one" << endl;
break;
case "second"_hash:
cout << "2nd one" << endl;
break;
case "third"_hash:
cout << "3rd one" << endl;
break;
default:
cout << "Default..." << endl;
}
demo
But please, think of the children! Unless you can guarantee that there will be no hash collisions, this is playing with fire and is not fit to be production code.
I'll assume your hash won't have any collisions for purposes of this answer.
If you instead define your hash as a preprocessor function, you can create constants you can match to.
This post might help: Compile-time (preprocessor) hashing of string
By definition, constant expression means an expression that can be evaluated at compile time.
But you can use case func(): if func() is constexpr.

How to construct a std::string with embedded values, i.e. "string interpolation"?

I want to create a string with embedded information. One way (not the only way) of achieving what I want is called string interpolation or variable substitution, wherein placeholders in a string are replaced with actual values.
In C, I would do something like this:
printf("error! value was %d but I expected %d",actualValue,expectedValue)
whereas if I were programming in python, I would do something like this:
"error! value was {0} but I expected {1}".format(actualValue,expectedValue)
both of these are examples of string interpolation.
How can I do this in C++?
Important Caveats:
I know that I can use std::cout if I want to print such a message to standard output (not string interpolation, but prints out the kind of string I want):
cout << "error! value was " << actualValue << " but I expected "
<< expectedValue;
I don't want to print a string to stdout. I want to pass a std::string as an argument to a function (e.g. the constructor of an exception object).
I am using C++11, but portability is potentially an issue, so knowing which methods work and don't work in which versions of C++ would be a plus.
Edit
For my immediate usage, I'm not concerned about performance (I'm raising an exception for cryin' out loud!). However, knowing the relative performance of the various methods would be very very useful in general.
Why not just use printf itself (C++ is a superset of C after all...)? This answer discusses some reasons why not. As far as I can understand, type safety is a big reason: if you put %d, the variable you put in there had better really be convertible to an integer, as that's how the function figures out what type it is. It would be much safer to have a method which uses compile-time knowledge of the actual type of the variables to be inserted.
In C++20 you will be able to use std::format.
This will support python style formatting:
string s = std::format("{1} to {0}", "a", "b");
There is already an implementation available: https://github.com/fmtlib/fmt.
Method 1: Using a string stream
It looks like std::stringstream gives a quick solution:
std::stringstream ss;
ss << "error! value was " << actualValue << " but I expected " << expectedValue << endl;
//example usage
throw MyException(ss.str())
Positive
no external dependencies
I believe this works in C++ 03 as well as c++ 11.
Negative
reportedly quite slow
a bit more messy: you must create a stream, write to it, and then get the string out of it.
Method 2: Boost Format
The Boost Format library is also a possibility. Using this, you would do:
throw MyException(boost::format("error! value was %1% but I expected %2%") % actualValue % expectedValue);
Positive
pretty clean compared to stringstream method: one compact construct
Negative
reportedly quite slow: uses the stream method internally
it's an external dependency
Edit:
Method 3: variadic template parameters
It seems that a type-safe version of printf can be created by using variadic template parameters (the techincal term for a template that takes an indefinite number of template parameters). I have seen a number of possibilities in this vein:
This question gives a compact example and discusses performance problems with that example.
This answer to that question, whose implementation is also quite compact, but reportedly still suffers from performance issues.
The fmt library, discussed in this answer, is reportedly quite fast and seems to be as clean as printf itself, but is an external dependency
Positive
usage is clean: just call a printf-like function
The fmt library is reportedly quite fast
The other options seem quite compact (no external dependency required)
Negative
the fmt library, while fast, is an external dependency
the other options apparently have some performance issues
In C++11 you can use std::to_string:
"error! value was " + std::to_string(actualValue) + " but I expected " + std::to_string(expectedValue)
It's not pretty, but it's straightforward, and you can use a macro to shrink it a bit. Performance is not great, since you do not reserve() space beforehand. Variadic templates would probably be faster and look nicer.
This kind of string construction (instead of interpolation) is also bad for localization, but you'd probably use a library if you needed that.
Use whatever you like:
1) std::stringstream
#include <sstream>
std::stringstream ss;
ss << "Hello world!" << std::endl;
throw std::runtime_error(ss.str());
2) libfmt : https://github.com/fmtlib/fmt
#include <stdexcept>
throw std::runtime_error(
fmt::format("Error has been detected with code {} while {}",
0x42, "copying"));
C++17 solution that works both for std::string & for std::wstring (Tested on VS2019 & VS2022):
#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>
template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
int size_signed{ 0 };
// 1) Determine size with error handling:
if constexpr (std::is_same_v<T, char>) { // C++17
size_signed = std::snprintf(nullptr, 0, format, args ...);
}
else {
size_signed = std::swprintf(nullptr, 0, format, args ...);
}
if (size_signed <= 0) {
throw std::runtime_error("error during formatting.");
}
const auto size = static_cast<size_t>(size_signed);
// 2) Prepare formatted string:
std::basic_string<T> formatted(size, T{});
if constexpr (std::is_same_v<T, char>) { // C++17
std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
else {
std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy.
}
// USE EXAMPLE: //
int main()
{
int i{ 0 };
const std::string example1 = string_format("string. number %d.", ++i); // => "string. number 1."
const std::wstring example2 = string_format(L"wstring. number %d.", ++i); // => L"wstring. number 2."
}
DISCLAIMER:
The subsequent code is based on an article I read 2 years ago. I will find the source and put it here ASAP.
This is what I use in my C++17 project. Should work with any C++ compiler supporting variadic templates though.
Usage:
std::string const word = "Beautiful";
std::string const message = CString::format("%0 is a %1 word with %2 characters.\n%0 %2 %0 %1 %2", word, "beautiful", word.size());
// Prints:
// Beautiful is a beautiful word with 9 characters.
// Beautiful 9 Beautiful beautiful 9.
The class implementation:
/**
* The CString class provides helpers to convert 8 and 16-bit
* strings to each other or format a string with a variadic number
* of arguments.
*/
class CString
{
public:
/**
* Format a string based on 'aFormat' with a variadic number of arbitrarily typed arguments.
*
* #param aFormat
* #param aArguments
* #return
*/
template <typename... TArgs>
static std::string format(
std::string const&aFormat,
TArgs &&...aArguments);
/**
* Accept an arbitrarily typed argument and convert it to it's proper
* string representation.
*
* #tparam TArg
* #tparam TEnable
* #param aArg
* #return
*/
template <
typename TArg,
typename TEnable = void
>
static std::string toString(TArg const &aArg);
/**
* Accept a float argument and convert it to it's proper string representation.
*
* #tparam TArg
* #param arg
* #return
*/
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
static std::string toString(const float& arg);
/**
* Convert a string into an arbitrarily typed representation.
*
* #param aString
* #return
*/
template <
typename TData,
typename TEnable = void
>
static TData const fromString(std::string const &aString);
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
static TData fromString(std::string const &aString);
private:
/**
* Format a list of arguments. In this case zero arguments as the abort-condition
* of the recursive expansion of the parameter pack.
*
* #param aArguments
*/
template <std::size_t NArgs>
static void formatArguments(std::array<std::string, NArgs> const &aArguments);
/**
* Format a list of arguments of arbitrary type and expand recursively.
*
* #param outFormatted
* #param inArg
* #param inArgs
*/
template <
std::size_t NArgs,
typename TArg,
typename... TArgs
>
static void formatArguments(
std::array<std::string, NArgs> &aOutFormatted,
TArg &&aInArg,
TArgs &&...aInArgs);
};
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename... TArgs>
std::string CString::format(
const std::string &aFormat,
TArgs &&...aArgs)
{
std::array<std::string, sizeof...(aArgs)> formattedArguments{};
formatArguments(formattedArguments, std::forward<TArgs>(aArgs)...);
if constexpr (sizeof...(aArgs) == 0)
{
return aFormat;
}
else {
uint32_t number = 0;
bool readNumber = false;
std::ostringstream stream;
for(std::size_t k = 0; k < aFormat.size(); ++k)
{
switch(aFormat[k])
{
case '%':
readNumber = true;
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
// Desired behaviour to enable reading numbers in text w/o preceding %
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
if(readNumber)
{
number *= 10;
number += static_cast<uint32_t>(aFormat[k] - '0');
break;
}
default:
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
stream << aFormat[k];
break;
#pragma GCC diagnostic warning "-Wimplicit-fallthrough"
}
}
if(readNumber)
{
stream << formattedArguments[std::size_t(number)];
readNumber = false;
number = 0;
}
return stream.str();
}
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <typename TArg, typename enable>
std::string CString::toString(TArg const &aArg)
{
std::ostringstream stream;
stream << aArg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TArg,
typename std::enable_if<std::is_floating_point<TArg>::value, TArg>::type
>
std::string CString::toString(const float& arg)
{
std::ostringstream stream;
stream << std::setprecision(12) << arg;
return stream.str();
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount>
void CString::formatArguments(std::array<std::string, argCount> const&aArgs)
{
// Unused: aArgs
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <std::size_t argCount, typename TArg, typename... TArgs>
void CString::formatArguments(
std::array<std::string, argCount> &outFormatted,
TArg &&inArg,
TArgs &&...inArgs)
{
// Executed for each, recursively until there's no param left.
uint32_t const index = (argCount - 1 - sizeof...(TArgs));
outFormatted[index] = toString(inArg);
formatArguments(outFormatted, std::forward<TArgs>(inArgs)...);
}
//<-----------------------------------------------------------------------------
//<-----------------------------------------------------------------------------
//<
//<-----------------------------------------------------------------------------
template <
typename TData,
typename std::enable_if
<
std::is_integral<TData>::value || std::is_floating_point<TData>::value,
TData
>::type
>
TData CString::fromString(std::string const &aString)
{
TData const result{};
std::stringstream ss(aString);
ss >> result;
return result;
}
//<-----------------------------------------------------------------------------
If you don't mind using a preprocessor script, here is a more easy but handy solution: https://github.com/crazybie/cpp_str_interpolation. Then you can write the code like this:
string s1 = "world", s2 = "!";
cout << _F("hello, {s1+s2}") << endl;
it also support using like a template engine:
int a = 1;
float b = 2.3f;
cout << _F(R"(
`for (int i=0; i<2; i++) {`
a is {a}, i is {i}.
a+i is {a+i}.
`}`
b is {b}.
cout << "123" << endl;`
)") << endl;
I've grown very fond of this solution, std::format notwithstanding. I dislike it on several counts (use of macros, and the whole concept of operator << overloading). But the ease of use truly makes up for it.
#ifndef SS_HPP
#define SS_HPP
#include <sstream>
#include <iostream>
// usage: SS("xyz" << 123 << 45.6) returning a std::string rvalue.
#define SS(x) ( ((std::stringstream&)(std::stringstream() << x )).str())
#endif

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

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