Пользовательская литеральная строка: проверка длины во время компиляции

У меня есть пользовательский литеральный оператор, который имеет смысл только для строк определенной длины, например:

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 вызвать ошибку во время компиляции?

5 ответов

Как я могу сделать "ABC"_int вызвать ошибку во время компиляции?

Например: инициализировать constexpr переменная

constexpr auto foo = "ABC"_int;

В противном случае (если вы каким-то образом не форсируете вычисление времени компиляции), компилятор не вычисляет (не обязательно, но фактически так и происходит) время компиляции, а подготавливает код для компиляции во время выполнения.

До 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
}

С С++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;
}
#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

Это было, к сожалению, не практично, чтобы оставлять комментарии.

Устраняет, кроме того, что ungood literal делает ошибку времени компиляции:

  • Исправлено смещение подписанных значений.
  • Использование 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.

Другие вопросы по тегам