I have written some code to cast const char*
to int
by using constexpr
and thus I can use a const char*
as a template argument. Here is the code:
#include <iostream> class conststr { public: template<std::size_t N> constexpr conststr(const char(&STR)[N]) :string(STR), size(N-1) {} constexpr conststr(const char* STR, std::size_t N) :string(STR), size(N) {} constexpr char operator[](std::size_t n) { return n < size ? string[n] : 0; } constexpr std::size_t get_size() { return size; } constexpr const char* get_string() { return string; } //This method is related with Fowler–Noll–Vo hash function constexpr unsigned hash(int n=0, unsigned h=2166136261) { return n == size ? h : hash(n+1,(h * 16777619) ^ (string[n])); } private: const char* string; std::size_t size; }; // output function that requires a compile-time constant, for testing template<int N> struct OUT { OUT() { std::cout << N << '\n'; } }; int constexpr operator "" _const(const char* str, size_t sz) { return conststr(str,sz).hash(); } int main() { OUT<"A dummy string"_const> out; OUT<"A very long template parameter as a const char*"_const> out2; }
In this example code, type of out
is OUT<1494474505>
and type of out2
is OUT<106227495>
. Magic behind this code is conststr::hash()
it is a constexpr
recursion that uses FNV Hash function. And thus it creates an integral hash for const char* which is hopefully a unique one.
I have some questions about this method:
const char*
to int constexpr
via conststr
and thus we will not need aesthetically ugly (and also time consumer) _const
user-defined string literal? For example OUT<"String">
will be legal (and cast "String" to integer).Any help will be appreciated, thanks a lot.
constexpr std::string While it's best to rely on string_views and not create unnecessary string copies, the example above shows that you can even create pass vectors of strings inside a constexpr function!
A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string.
Although your method is very interesting, it is not really a way to pass a string literal as a template argument. In fact, it is a generator of template argument based on string literal, which is not the same: you cannot retrieve string
from hashed_string
... It kinda defeats the whole interest of string literals in templates.
EDIT : the following was right when the hash used was the weighted sum of the letters, which is not the case after the edit of the OP.
You can also have problems with your hash function, as stated by mitchnull's answer. This may be another big problem with your method, the collisions. For example:
// Both outputs 3721 OUT<"0 silent"_const> out; OUT<"7 listen"_const> out2;
As far as I know, you cannot pass a string literal in a template argument straightforwardly in the current standard. However, you can "fake" it. Here's what I use in general:
struct string_holder // { // All of this can be heavily optimized by static const char* asString() // the compiler. It is also easy to generate { // with a macro. return "Hello world!"; // } // }; //
Then, I pass the "fake string literal" via a type argument:
template<typename str> struct out { out() { std::cout << str::asString() << "\n"; } };
EDIT2: you said in the comments you used this to distinguish between several specializations of a class template. The method you showed is valid for that, but you can also use tags:
// tags struct myTag {}; struct Long {}; struct Float {}; // class template template<typename tag> struct Integer { // ... }; template<> struct Integer<Long> { /* ... */ }; // use Integer<Long> ...; // those are 2 Integer<Float> ...; // different types
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With