Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using "constexpr" to use string literal for template parameter

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:

  1. Is this a safe approach to use? Or can this approach be an evil in a specific use?
  2. Can you write a better hash function that creates different integer for each string without being limited to a number of chars? (in my method, the length is long enough)
  3. Can you write a code that implicitly casts 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.

like image 352
Equalities of polynomials Avatar asked Apr 07 '13 11:04

Equalities of polynomials


People also ask

Can strings be Constexpr?

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!

Is string a literal?

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.


1 Answers

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 
like image 52
Synxis Avatar answered Oct 12 '22 02:10

Synxis