Using "constexpr" to use string literal for template parameter

Equalities of polynomials picture Equalities of polynomials · Apr 7, 2013 · Viewed 14.9k times · Source

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.

Answer

Synxis picture Synxis · Apr 7, 2013

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