使用“constexpr”将字符串字面值用作模板参数

27

我已经写了一些代码,使用constexprconst char *转换为int,因此我可以使用const char *作为模板参数。这是代码:

#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;
}
在这个示例代码中,out的类型是OUT<1494474505>,而out2的类型是OUT<106227495>。这段代码背后的魔法是conststr::hash(),它是一个使用FNV哈希函数constexpr递归函数。因此,它为const char*创建了一个整数哈希值,希望这是唯一的。

我有一些关于这种方法的问题:

  1. 这是一种安全的使用方式吗?或者在特定情况下,这种方法可能会产生一些问题吗?
  2. 你能否编写更好的哈希函数,使每个字符串都创建不同的整数,而不限制字符数?(在我的方法中,长度足够长)
  3. 你能否编写一段代码,通过conststrconst char*隐式转换为int constexpr,从而我们将不需要美观丑陋(而且也浪费时间)的_const用户定义的字符串字面量?例如OUT<"String">将是合法的(并将"String"转换为整数)。

非常感谢您的帮助。


如果我的回答“解决”了您的问题,可以将其标记为已接受吗?否则,您可以再次对其进行评论! - Synxis
哦,抱歉,我总是忘记这样做 :) - Equalities of polynomials
2个回答

13
尽管您的方法非常有趣,但它实际上并不是将字符串字面值作为模板参数传递的方法。事实上,它是基于字符串字面值生成模板参数的生成器,这并不相同:您无法从hashed_string中检索字符串。 这有点破坏了模板中字符串字面值的整个兴趣。
编辑:在哈希使用字母加权和时,以下内容是正确的,这在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;
据我所知,在当前标准中,你无法直接将字符串字面值传递为模板参数。但是,你可以“伪造”它。以下是我通常使用的方法:
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!";    //
    }                             //
};                                //

然后,我通过类型参数传递“假字符串文字”:

template<typename str>
struct out
{
    out()
    {
        std::cout << str::asString() << "\n";
    }
};

编辑2:您在评论中提到,您使用此方法来区分类模板的多个特化。您展示的方法对此有效,但您也可以使用标签:

// 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

1
你不需要将字符串传递给 OUT,而是需要传递 hash() 的结果。我认为你无法将这个值转换为字符串... 因此,你不能让 OUT 输出你在括号中写的字符串。 - Synxis
好的,如果这是你想要的(但仍然是一个有效的观点 ;))。这些字符串“ids”(“long”,“short”等)是否有固定数量(有限的,例如10-20个不同的ids)? - Synxis
实际上是的,我认为它们应该是有限的,因为我手动专门处理了每一个模板。我可以使用Integer <1>(代表Integer <“short”>),和Integer <2>(代表Integer <“long”>)但是你不认为使用字符串字面量在美学上更好看且更易读吗?人们可能会忘记Integer <1>是什么,但是Integer <“short”>是不言自明的。 - Equalities of polynomials
1
您可以使用标签。我会在我的答案中加入它。 - Synxis
1
好的观点。我更喜欢标签,因为它比字面版本更“美观”,但选择权在于你。你也可以选择一个不同的字面名称,例如 _tag - Synxis
显示剩余3条评论

9

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接