用户定义的文字字符串:编译时长度检查

问题描述 投票:0回答:5

我有一个用户定义的文字运算符,仅对特定长度的字符串有意义,如下所示:

constexpr uint16_t operator "" _int(const char* s, std::size_t len)
{
    return len == 2 ? s[0] | (s[1] << 8) : throw;
}

这有效:

"AB"_int // equals 16961

但这也可以编译,但我不希望它这样:

"ABC"_int // throws at runtime

我尝试了

static_assert(len == 2)
,但在 constexpr 函数中不允许这样做。

如何让

"ABC"_int
在编译时导致错误?

c++ c++11 static-assert compile-time-constant user-defined-literals
5个回答
2
投票

如何让

"ABC"_int
在编译时导致错误?

举例:初始化一个

constexpr
变量

constexpr auto foo = "ABC"_int;

否则(如果您不以某种方式强制计算编译时间)编译器不会计算(不是强制性的,但实际上会发生)编译时间,而是为运行时编译准备代码。


1
投票

在 C++20 之前,可以用宏包裹

std::integral_constant
,使
throw
触发编译错误。

    constexpr uint16_t operator "" _int(const char* s, std::size_t len)
    {
        return len == 2 ? s[0] | (s[1] << 8) : throw;
    }

    void test()
    {
#define FORCE_CONSTANT(val) std::integral_constant<decltype(val), (val)>::value

        FORCE_CONSTANT("AB"_int);
        // FORCE_CONSTANT("ABC"_int); // error, expected compile-time constant expression
    }

从 C++20 开始,事情变得容易了,用户定义的文字被允许是

string literal operator template
。 (cpref)

因此,以下代码将按您的预期工作。

template <size_t kCount>
struct template_str_buffer
{
    using char_type = char;

    consteval template_str_buffer(const char_type(&str)[kCount]) noexcept
    {
        for (size_t i = 0; i < kCount; ++i) {
            data[i] = str[i];
        }
    }

    char_type data[kCount];
    constexpr static size_t count = kCount - sizeof(char_type);
};

template <template_str_buffer kStrBuf>
consteval uint16_t operator""_int()
{
    static_assert(kStrBuf.count == 2);
    return kStrBuf.data[0] | (kStrBuf.data[1] << 8);
}

void test()
{
    "AB"_int;
    // "ABC"_int; // static assertion failed
}

0
投票
#include <iostream>
#include <cstdint>
using namespace std;

constexpr uint16_t operator "" _int(char const * s, size_t len)
{
    return (len == 2) ? s[0] | (s[1] << 8) : throw "len must be 2!";
}

int main()
{
    constexpr uint16_t i1 = "AB"_int; // OK
    cout << i1 << endl; // outputs 16961

    constexpr uint16_t i2 = "ABC"_int; // error
    cout << i2 << endl;

    return 0;
}

prog.cpp: In function ‘int main()’:
prog.cpp:13:29:   in constexpr expansion of ‘operator""_int(((const char*)"ABC"), 3ul)’
prog.cpp:7:52: error: expression ‘<throw-expression>’ is not a constant-expression
     return (len == 2) ? s[0] | (s[1] << 8) : throw "len must be 2!";
                                                    ^~~~~~~~~~~~~~~~

现场演示


0
投票

不幸的是,作为评论发布是不切实际的。

除了使不良文字成为编译时错误之外还进行了修复:

  • 修复了有符号值的移位。
  • 不带参数使用
    throw
  • 明确假设 8 位字节。
#include <iostream>
#include <stdint.h>
#include <limits.h>         // CHAR_BIT
using namespace std;

using Byte = unsigned char;
const int bits_per_byte = CHAR_BIT;

static_assert( bits_per_byte == 8, "!" );

constexpr auto operator "" _int( char const* s, std::size_t len )
    -> uint16_t 
{ return len == 2 ? Byte( s[0] ) | (Byte( s[1] ) << 8u) : throw "Bah!"; }

#define CHAR_PAIR( s ) static_cast<uint16_t>( sizeof( char[s ## _int] ) )

auto main()
    -> int
{
    CHAR_PAIR( "AB" );              // OK
    CHAR_PAIR( "ABC" );             //! Doesn't compile as ISO C++.
}

使用 Visual C++ 这就是所需要的。

g++ 在这方面不太符合标准,因此对于该编译器添加选项

-Werror=vla

使用 g++,您也可以使用以下宏:

#define CHAR_PAIR( s ) []() constexpr { constexpr auto r = s##_int; return r; }()

这会提供更丰富的错误消息,但 Visual C++ 2017 不支持。


0
投票

使用 C++20,您可以使用

consteval
和普通
assert
(或任何您喜欢的例外):

#include <iostream>
#include <cstdint>
#include <cassert>

consteval uint16_t operator "" _int(const char* s, size_t len)
{
    assert(len == 2);
    return s[0] | (s[1] << 8);
}

int main() {
    std::cout << "AB"_int << std::endl;
    //std::cout << "ABC"_int << std::endl;  // compiler error

    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.