C++11中constexpr函数中的常量表达式字符串参数

5
我正在尝试制作一个constexpr函数,将UUID字符串(例如"f6ece560-cc3b-459a-87f1-22331582216e")转换为类似于以下内容的类:
class UUID {
public:
      explicit UUID(uint8_t bytes[]); // Must be 16 byte array.

这是我目前所得到的:

// Compile time hex conversion of a single character into a nibble (half-byte).
constexpr uint8_t hexToNibble(char a)
{
    // Does not work:
//  static_assert(a >= '0' && a <= '9' || a >= 'a' && a <= 'f' || a >= 'A' && a <= 'F', "Invalid hex character");
    return a >= '0' && a <= '9' ? (a - '0') :
           a >= 'a' && a <= 'f' ? (a - 'a' + 10) :
           a >= 'A' && a <= 'F' ? (a - 'A' + 10) : 0;
}

// Compile time hex conversion of two characters into a byte.
constexpr uint8_t hexToByte(char a, char b)
{
    return (hexToNibble(a) << 4) + hexToNibble(b);
}

// Compile time string length.
constexpr int strlenConst(const char* str)
{
    return *str ? 1 + strlenConst(str + 1) : 0;
}

// Convert a UUID string to an array of bytes.
// Example: "f6ece560-cc3b-459a-87f1-22331582216e".
constexpr std::array<uint8_t, 16> UUIDFromString(const char* str)
{
    // This does not work:
//  static_assert(strlenConst(str) == 36, "Invalid GUID length");

    return std::array<uint8_t, 16>{
        hexToByte(str[0], str[1]),
        hexToByte(str[2], str[3]),
        hexToByte(str[4], str[5]),
        hexToByte(str[6], str[7]),
        hexToByte(str[9], str[10]),
        hexToByte(str[11], str[12]),
        hexToByte(str[14], str[15]),
        hexToByte(str[16], str[17]),
        hexToByte(str[19], str[20]),
        hexToByte(str[21], str[22]),
        hexToByte(str[24], str[25]),
        hexToByte(str[26], str[27]),
        hexToByte(str[28], str[29]),
        hexToByte(str[30], str[31]),
        hexToByte(str[32], str[33]),
        hexToByte(str[34], str[35]),
    };
}

#define MAKE_UUID(var, str) \
    static_assert(strlenConst(str) == 36, "Invalid GUID length for " #var); \
    const UUID var(UUIDFromString(str).data());

// Works but doesn't check string length.
const UUID UUID_1(UUIDFromString("f6ece560-cc3b-459a-87f1-22331582216e").data());

// Checks string length but uses an ugly macro.
MAKE_UUID(UUID_2, "f6ece560-cc3b-459a-87f1-22331582216e")

正如您所看到的,存在一个问题 - 在constexpr函数中似乎不可能有常量表达式作为函数参数,因此即使传入的值是常量表达式,也无法对参数进行static_assert检查。
因此,我采用了宏来检查字符串长度,并放弃了对字符的检查。
有没有解决这个问题的方法?还有,如何轻松确保该函数实际上在编译时被评估?
编辑:这与C++11 - static_assert within constexpr function?不同-或者至少相同的答案不起作用-请参见下面的评论。
编辑2:Shafik的出色答案适用于大小问题,但不适用于检查十六进制字符。据我所知,即使使用此...
// Compile time hex conversion of a single character into a nibble (half-byte).
template<char a>
constexpr uint8_t hexToNibble()
{
    static_assert(a >= '0' && a <= '9' || a >= 'a' && a <= 'f' || a >= 'A' && a <= 'F', "Invalid hex character");
    return a >= '0' && a <= '9' ? (a - '0') :
           a >= 'a' && a <= 'f' ? (a - 'a' + 10) :
           a >= 'A' && a <= 'F' ? (a - 'A' + 10) : 0;
}

// Compile time hex conversion of two characters into a byte.
template<char a, char b>
constexpr uint8_t hexToByte()
{
    return (hexToNibble<a>() << 4) + hexToNibble<b>();
}

这不起作用:

// Convert a UUID string to an array of bytes.
// Example: "f6ece560-cc3b-459a-87f1-22331582216e".
template <size_t N>
constexpr std::array<uint8_t, 16> UUIDFromString(const char (&str)[N])
{
    // Note you have to include the null byte.
    static_assert(N == 37, "Invalid GUID length.");

    return std::array<uint8_t, 16>{
        hexToByte<str[0], str[1]>(),
        hexToByte<str[2], str[3]>(),

因为str[0]不是常量表达式。

2
可能是C++11 - static_assert within constexpr function?的重复问题。请查看使用模板的第二个答案。 - AndyG
@AndyG 对于这种情况无法工作,因为 OP 正在使用字符串字面量,它们无法绑定到模板非类型参数。 - Shafik Yaghmour
@AndyG 我认为这实际上是一个比你链接到的一般情况更具体的不同问题。 - Shafik Yaghmour
1
顺便提一下,在 hexToByte 函数中,那个移位应该是 4,而不是 8。 - Drew McGowen
@ShafikYaghmour:谢谢你提供的信息! - AndyG
2个回答

5
如AndyG在这里指出的那样,问题C++11 - static_assert within constexpr function?告诉我们一种方法是使用非类型模板参数,这些参数必须在编译时可用。
这种解决方案的问题是原始提问者正在使用字符串字面值,它们不能绑定到非类型参数上

特别地,这意味着字符串字面值、数组元素的地址和非静态成员的地址都不能用作模板参数来实例化对应非类型模板参数为对象指针的模板。

其中一个解决方法是不直接使用字符串字面值,而是使用对问题重要的属性——数组长度,如下所示:
template <size_t N>
constexpr std::array<uint8_t, 16> UUIDFromString( const char (&str)[N])
{
  static_assert(N == 36, "Invalid GUID length");

  //....
}

这个可行!我必须将36更改为37,因为我猜它包括空终止符。尽管我使用了template<char a> uint8_t hexToNibble() {等等,但我仍然无法检查十六进制字符,因为我不能执行hexToByte<str[1], str[0]>();,因为str[1]仍然不是常量表达式。也许这是不可能的。 - Timmmm
@Timmmm,非常抱歉,我完全忽略了第一个static_assert。我会尝试在今天稍后查看它,但也许它是无法解决的。 - Shafik Yaghmour

3

如果你想使用 static_assert,那么你必须对相关参数进行模板参数化。

有人曾经指出,在非类型模板参数中无法绑定字符串字面量,这在法律上是正确的,但是你可以通过一些迂回的方式来实现。

以下是一个草图:

#include <cstddef>
#include <array>

constexpr char const f6ece560_cc3b_459a_87f1_22331582216e[] =
    "f6ece560-cc3b-459a-87f1-22331582216e";

constexpr int strlenConst(const char* str)
{
    return *str ? 1 + strlenConst(str + 1) : 0;
}

template<char const * Hex>
struct UUID {
    static constexpr char const * hex = Hex;
    static constexpr std::size_t len = strlenConst(hex);
    static_assert(len == 36,"Invalid GUID length");
    template<char Ch>
    static constexpr uint8_t hexToNibble() {
        static_assert((Ch >= '0' && Ch <= '9') || (Ch >= 'a' && Ch <= 'f')
                    || (Ch >= 'A' && Ch <= 'F'), "Invalid hex character");
        return Ch >= '0' && Ch <= '9' ? (Ch - '0') :
               Ch >= 'a' && Ch <= 'f' ? (Ch - 'a' + 10) :
               Ch >= 'A' && Ch <= 'F' ? (Ch - 'A' + 10) : 0;
    }
    template<char First, char Second>
    static constexpr uint8_t hexToByte()
    {
        return (hexToNibble<First>() << 4) + hexToNibble<Second>();
    }
    static constexpr std::array<uint8_t, 16> get() {
        return std::array<uint8_t, 16>{{
            hexToByte<hex[0], hex[1]>(),
            hexToByte<hex[2], hex[3]>(),
            hexToByte<hex[4], hex[5]>(),
            hexToByte<hex[6], hex[7]>(),
            hexToByte<hex[9], hex[10]>(),
            hexToByte<hex[11], hex[12]>(),
            hexToByte<hex[14], hex[15]>(),
            hexToByte<hex[16], hex[17]>(),
            hexToByte<hex[19], hex[20]>(),
            hexToByte<hex[21], hex[22]>(),
            hexToByte<hex[24], hex[25]>(),
            hexToByte<hex[26], hex[27]>(),
            hexToByte<hex[28], hex[29]>(),
            hexToByte<hex[30], hex[31]>(),
            hexToByte<hex[32], hex[33]>(),
            hexToByte<hex[34], hex[35]>()
        }};
    }
};

#include <iostream>

using namespace std;

int main()
{
    using some_uuid_t = UUID<f6ece560_cc3b_459a_87f1_22331582216e>;
    cout << some_uuid_t::hex << endl;
    auto uuid = some_uuid_t::get();
    for (auto const & byte : uuid) {
        cout << int(byte);
    }
    cout << endl;
    return 0;
}

(gcc 4.9.2/clang 3.5.2 -std=c++11 -Wall -pedantic) 可以翻译为:(gcc 4.9.2/clang 3.5.2 -std=c++11 -Wall -pedantic)。

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