Пользовательская литеральная строка: проверка длины во время компиляции
У меня есть пользовательский литеральный оператор, который имеет смысл только для строк определенной длины, например:
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!";
^~~~~~~~~~~~~~~~
Это было, к сожалению, не практично, чтобы оставлять комментарии.
Устраняет, кроме того, что 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.