Why yet another question on string to hash conversion
My question is basically the same as the popular How to convert a std::string to const char* or char* question, but with a twist. I need the hash at compile time. Before rejecting my question, let me briefly explain my motivation.
Motivation
In my framework I am building, I have many threads. I have carefully designed the file structure so these threads re-use files whose functionality are the same so as not to violate ODR and lose maintainability. At the top of the list is error logging. My elaborate code begs to be re-used as-is in these different apps. So the initialized errorLogger object needs to be a different instance for each thread.
Proposed solution
Templatize my ErrorLogger class with a constant non-type parameter. In my framework, each app has a unique string that identifies itself. Now if I could hash that string at compile time, I would have the non-type template parameter I need for the compiler to generate separate instances.
Here is the example code that doesn't work:
#include <string>
std::string threadUniqueStr { "threadUniqueName" };
/*constexpr*/ auto threadUniqueHash = std::hash< std::string > {} ( threadUniqueStr ); // uncommented constexpr results in 'expression did not evaluate to a constant'
template< size_t >
void Func()
{}
int main()
{
//Func< threadUniqueHash >(); // ERROR: expression did not evaluate to a constant
Func< 33 >(); // works fine
}
But maybe there is an easier C++ way to do this that I am overlooking?
Edit 1: My Solution
Answer 1 shows how to create a hash from a string using string_view which follows @NathanOliver's advice that you have to write your own hash function for it to be constexpr. But I understand that writing your own hash function can have problems. @Pepijn Kramer points out 1) two strings may still produce the same hash and 2) from his experience, that a class hierarchy featuring app reporting at the top and individual error reporting behavior derived classes served his purposes in multi-dev situations (like mine). Since I don't want to use templates non-type parameter feature in an un-tried manner even though I can make a case for it, I am going to create my own ErrorLogger class hierarchy. Thanks to all for your helpful input.
Edit 2: My Solution 2
I ended up using my original design for my error logger. Answer 1's string_view hash lets me constexpr a unique enough hash number which I use to create explicit template specializations, one for each named project. The ErrorLogger code itself is put into a static inside the specialization. Here is what the coding structure looks like:
// .h
template< size_t ProjectNameNumT > // primary non-type template global func
void INFOMSG();
template< size_t ProjectNameNumT >
void INFOMSG( bool yesNo ); // 2nd primary template; 1st overload
// global define in Proj A
ErrorLogger< PROJ_A_HASH > errorLoggerProjA;
// global define in Proj B
ErrorLogger< PROJ_B_HASH > errorLoggerProjB;
// .cpp
template<> void INFOMSG< PROJ_A_HASH >()
{
errorLoggerProjA.initLoggerHasRun = true; // set bool in specialization A specialization
}
// .cpp
template<> void INFOMSG< PROJ_B_HASH >()
{
errorLoggerProjB.initLoggerHasRun = true; // set bool in specialization B specialization
}
// .cpp
template<> void INFOMSG< PROJ_B_HASH >( bool yesNo )
{
errorLogger.initLoggerHasRun = yesNo; // uses
}
// dev user's interface
INFOMSG< PROJ_A_HASH >(); // sets bool to true in A
INFOMSG< PROJ_B_HASH >(); // sets bool to true in B
INFOMSG< PROJ_A_HASH >( false ); // sets bool in A to whatever yesNo value which is false here
The ODR goal was achieved without sacrificing dev interface ease-of-use.