【问题标题】:User-defined literal string: compile-time length check用户定义的文字字符串:编译时长度检查
【发布时间】:2018-05-12 14:51:36
【问题描述】:

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

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 在编译时导致错误?

【问题讨论】:

  • 如果与静态断言一起使用 constexpr?使用len作为模板参数,std::enable_if?
  • @SamVarshavchik,参数在常量表达式中不可用。 constexpr 函数意味着在编译时和运行时都可以使用。还有更多空间,但这就是我们现在所拥有的一切。
  • 如果您使用 clang 和 g++ 支持的模板化 UDL 扩展(可能会成为未来的标准),您可以这样做。
  • 这里的无参数 throw 是一个 rethrow,它应该终止程序,除非它恰好在某个地方的 catch 子句中。
  • 声明const size_t len会有区别吗?

标签: c++ c++11 static-assert compile-time-constant user-defined-literals


【解决方案1】:

如何让"ABC"_int 在编译时出错?

举例:初始化一个constexpr变量

constexpr auto foo = "ABC"_int;

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

【讨论】:

  • @MarkRansom - 哦!谢谢。
  • 这不能回答问题。这是 OP 显然不知道的有用信息,但这不是答案。应该是评论;标记为这样。
  • @Cheersandhth.-Alf - 在我看来,回答了这个问题;显然,如果 OP 打算“我怎样才能让“ABC”_int 在编译时导致 ever 出错?”,并没有给出解决方案,而是解释(在我看来很清楚)这是不可能的。等待版主操作。
  • 我意识到使用constexpr 中的值会使"ABC"_int 无法编译,但我希望"ABC"_int 即使在constexpr 之外使用也无法编译。另一种说法是,我希望运算符始终被评估为constexpr,如果不能,它应该无法编译。
  • @JohnZwinck - 抱歉:以这种形式(char const * 和大小的文字运算符)我认为这是不可能的(目前)。如果您对数字感兴趣(12_int 而不是 "AB"_int),则可以使用字符串文字的模板形式(例如,static_assert())。但我想你也对所有字符感兴趣。
【解决方案2】:
#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!";
                                                    ^~~~~~~~~~~~~~~~

Live Demo

【讨论】:

  • 这不能回答问题。这是 OP 显然不知道的有用信息,但这不是答案。应该是评论;标记为这样。
  • @Cheersandhth.-Alf 它确实回答了以下问题:“我怎样才能让"ABC"_int 在编译时导致错误?”。你甚至尝试过吗?错误信息不好看,但它确实只出现在"ABC"_int"AB"_int 编译良好并输出所需的输出
  • 问题是“如何让“ABC”_int 在编译时导致错误”。您尝试的答案是,在一个特定的上下文中,初始化一个 constexpr 值,它会在编译时导致错误。 OP 明确要求提供一种通用机制,而不是一个特定的特殊上下文。
  • 所以,你应该解释你用代码表达的内容(你能想到的单一特殊上下文),并将其作为评论发布,并删除这个尝试的答案。
【解决方案3】:

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

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

  • 已修复有符号值的移位。
  • 使用不带参数的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 不支持。

【讨论】:

  • 所以您依赖 throw 作为三元的无效表达式?这很聪明。
  • 乍一看,这正是 OP 正在做的事情。你的有什么不同?
  • @Mark:operator""_int 实现的三个不同点列在要点中。该代码显示了如何获得 OP 想要的编译时间检查,但不幸的是不是他喜欢的语法。这就是为什么这也不是一个答案(字面上的答案只是“对不起,这不可能”),但不幸的是,将所有这些都塞进 SO 评论中是不切实际的。
【解决方案4】:

在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。 (cppref)

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

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
}

【讨论】:

    猜你喜欢
    • 2011-10-20
    • 2021-05-23
    • 1970-01-01
    • 2011-04-29
    • 2015-06-23
    • 1970-01-01
    • 1970-01-01
    • 2015-09-11
    • 1970-01-01
    相关资源
    最近更新 更多