26

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.

2 Answers2

13

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
Community
  • 1
  • 1
Synxis
  • 9,236
  • 2
  • 42
  • 64
  • Synxis, I edited my code. My hash function was an unprofessional one, now it is using Fowler–Noll–Vo hash function. I believe this function does not have the same problem. And I could not understand your first paragraph that about ´string´ from ´hashed_string´? – Equalities of polynomials Apr 07 '13 at 13:14
  • 1
    You do not pass the string to `OUT`, but the result of `hash()`. I don't think you can transform this value to a string... So, you cannot have `OUT` outputting the string you wrote between the brackets. – Synxis Apr 07 '13 at 13:17
  • Oh I see. Actually that's not a problem for me (of course your point is valid) because I want to make templates that work like this: let's say we have a template named Integer<>, Integer<"long"> will be something different than Integer<"short"> and I will specialize my template to behave so. – Equalities of polynomials Apr 07 '13 at 13:24
  • Ok, if it is want you want (but still a valid point ;) ). Are such string "ids" ("long", "short", etc...) in a fixed number (finite, like 10-20 different ids) ? – Synxis Apr 07 '13 at 13:28
  • Actually yes, and I believe they should be finite because I specialize every single of those templates manually. I can use Integer<1> (which symbolizes Integer<"short"> let's say), and Integer<2> (which symbolizes Integer<"long"> let's say) but don't you think that using string literals are aesthetically better and easier to read. One can forget what Integer<1> is, but Integer<"short"> is self-explanatory. – Equalities of polynomials Apr 07 '13 at 13:33
  • 1
    You can use tags. I will put that in my answer. – Synxis Apr 07 '13 at 13:40
  • Yeah thank you for reminding this method too. I have once considered something similar, but did not write a code because since I am using namespace, tags' names also get long. For example let's say I am writing my code in "nmsp" namespace, user shall write nmsp::Integer; which is nearly same (in terms of length) with nmsp::Integer<"long"_const> and I don't mind using C++11, because my code will not work without C++11 anyway. – Equalities of polynomials Apr 07 '13 at 13:55
  • 1
    Good point. I prefer the tags, because it is more "aesthetic" than the literal version, but that choice is up to you. You can also choose a different literal name, for example `_tag`. – Synxis Apr 07 '13 at 14:07
9

Here is the pattern that I am using for template const string parameters. class F { static constexpr const char conststr[]= "some const string"; TemplateObject<conststr> instance; };

see : https://stackoverflow.com/a/18031951/782168

h4ck3rm1k3
  • 2,060
  • 22
  • 33