OpenSiv3D
OpenSiv3D copied to clipboard
3'000_big, 0xabp+4_bigF などをコンパイルエラーにする、もしくは対応する
提案
operator""_big 、 operator""_bigF はそれぞれ boost::multiprecision::cpp_int 、 boost::multiprecision::cpp_dec_float_100 の assign メンバ関数にパラメータを渡すので、それらが対応していない形式の文字列が渡された場合は std::runtime_error が投げられます。
これにより、 3'000_big や 0xabp+4_bigF などは、 C++20 のリテラルとしては正しいためコンパイルは通りますが、実行時エラーが発生します。
コンストラクタや assign メンバ関数に渡された文字列が不正な場合は仕方ありませんが、リテラルの場合、値がコンパイル時に分かるのでコンパイルエラーにできるはずです。
よって、 operator""_big および operator""_bigF にコンパイル時チェックを導入するか、 C++20 の整数リテラルおよび浮動小数点数リテラルの形式に対応することを提案します。
# include <Siv3D.hpp>
# define check(expr) try { Print << #expr U": {}"_fmt(expr); } catch (const std::runtime_error&) { Print << #expr U": runtime error!"; }
void Main()
{
check( 123_big ); // ok
check( 0173_big ); // ok
check( 0x7b_big ); // ok
check( 0b01111011_big ); // runtime error!
check( 1'2'3_big ); // runtime error!
//check( 123.0_big ); // compile error (#826)
check( 123_bigF ); // ok
check( 0123_bigF ); // ok, ただし BigInt とは異なり10進法でパースされる
check( 123.0_bigF ); // ok
check( 123._bigF ); // ok
check( .123e+3_bigF ); // ok
check( 0x7b_bigF ); // runtime error!
check( 0x7bp0_bigF ); // runtime error!
check( 0b01111011_bigF ); // runtime error!
check( 1'2'3.0_bigF ); // runtime error!
while (System::Update());
}
コンパイルエラーにする
リテラル演算子をリテラル演算子テンプレートに変更して、文字列をコンパイル時にチェックしてエラーにする案です。
static_assert を使う
対応している形式でないとき、 static_assert でコンパイルエラーにする。
良い点
- シンプルに実装できる
- エラーメッセージが書ける
悪い点
- エラーの行番号が
static_assertを書いた行のものになる

実装例
クリックでコードを開く/閉じる
// BigInt.hpp
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
[[nodiscard]]
BigInt operator ""_big();
[[nodiscard]]
BigInt operator ""_big(long double) = delete;
}
}
// BigFloat.hpp
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
[[nodiscard]]
BigFloat operator ""_bigF();
}
}
// detail/BigInt.ipp
namespace detail
{
// テンプレート特殊化でチェックする例
// 美しいが、複雑なものには向かない
template <char... S>
struct IsDecimalLiteral : std::bool_constant<(IsDigit(S) && ...)> {};
template <>
struct IsDecimalLiteral<> : std::bool_constant<false> {};
template <char... S>
struct IsHexadecimalLiteral : std::bool_constant<false> {};
template <char X>
struct IsHexadecimalLiteral<'0', X> : std::bool_constant<false> {};
template <char... S>
struct IsHexadecimalLiteral<'0', 'x', S...> : std::bool_constant<(IsXdigit(S) && ...)> {};
template <char... S>
struct IsHexadecimalLiteral<'0', 'X', S...> : std::bool_constant<(IsXdigit(S) && ...)> {};
template <char... S>
constexpr bool IsBigIntLiteral = IsDecimalLiteral<S...>::value || IsHexadecimalLiteral<S...>::value;
}
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
BigInt operator ""_big()
{
static_assert(detail::IsBigIntLiteral<S...>);
using CSTR = const char[];
return BigInt{ std::string_view{ CSTR{ S..., '\0' } } };
}
}
}
// detail/BigFloat.ipp
namespace detail
{
// constexpr 関数でチェックする例
// 美しくないが、複雑なものにも対応できる
template <char... S>
constexpr bool IsBigFloatLiteral()
{
using CSTR = const char[];
const std::string_view s{ CSTR{ S..., '\0' } };
auto it = s.begin();
const auto end = s.end();
// (\.\d+|\d+\.?\d*)([eE][+-]?\d+)?
// (\.\d+|\d+\.?\d*)
if (it == end) return false;
if (*it == '.') // \.\d+
{
// \.
++it;
// \d+
if (it == end || not IsDigit(*it)) return false;
++it;
while (it != end && IsDigit(*it)) ++it;
}
else // \d+\.?\d*
{
// \d+
if (it == end || not IsDigit(*it)) return false;
++it;
while (it != end && IsDigit(*it)) ++it;
// \.?
if (it != end && *it == '.') ++it;
// \d*
while (it != end && IsDigit(*it)) ++it;
}
// ([eE][+-]?\d+)?
if (it != end) // [eE][+-]?\d+
{
// [eE]
if (it == end || ToLower(*it) != 'e') return false;
++it;
// [+-]?
if (it != end && (*it == '+' || *it == '-')) ++it;
// \d+
if (it == end || not IsDigit(*it)) return false;
++it;
while (it != end && IsDigit(*it)) ++it;
}
return it == end;
}
}
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
BigFloat operator ""_bigF()
{
static_assert(detail::IsBigFloatLiteral<S...>());
using CSTR = const char[];
return BigFloat{ std::string_view{ CSTR{ S..., '\0' } } };
}
}
}
( char を s3d::char32 に暗黙キャストしてるの、不安すぎる)
std::enable_if を使う
static_assert の代わりに std::enable_if を使う。
良い点
- エラーにリテラルを書いた行の行番号が出る
悪い点
- リテラル演算子テンプレートの宣言からすべてのチェックの実装が見えている必要があるので、 detail に逃がせず、ヘッダが散らかる

実装例
クリックでコードを開く/閉じる
// BigInt.hpp
// static_assert のほうの実装例で定義したものを含める
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
[[nodiscard]]
std::enable_if_t<detail::IsBigIntLiteral<S...>, BigInt>
operator ""_big()
{
using CSTR = const char[];
return BigInt{ std::string_view{ CSTR{ S..., '\0' } } };
}
[[nodiscard]]
BigInt operator ""_big(long double) = delete;
}
}
// BigFloat.hpp
// static_assert のほうの実装例で定義したものを含める
inline namespace Literals
{
inline namespace BigNumLiterals
{
template <char... S>
[[nodiscard]]
std::enable_if_t<detail::IsBigFloatLiteral<S...>(), BigFloat>
operator ""_bigF()
{
using CSTR = const char[];
return BigFloat{ std::string_view{ CSTR{ S..., '\0' } } };
}
}
}
対応する
Boost が対応してないなら、 Siv3D が対応するのもありかなと思います。
BigInt::assign, BigFloat::assign を変更する
対応する必要があるものを挙げます。
'を含む整数、浮動小数点数'を無視するだけでいいので、楽そう
- 2進数リテラル
- 16進数は
boost::multiprecision::cpp_intが対応してるので、16進数に置き換えると楽そう
- 16進数は
- 16進浮動小数点数リテラル
- 仮数部と指数部を分けて、
BigInt{ (仮数部) } * Math::Pow(BigFloat{ 2 }, BigFloat{ (指数部) })をすればよさそう
- 仮数部と指数部を分けて、
- (8進数リテラル)
- エラーにはならないが、
BigIntだと8進数、BigFloatだと10進数扱いなので、対応した方がいいかも - でも C++ も
0123 == 83だけど0123. == 123みたいな感じなので、対応しなくてもいいかも
- エラーにはならないが、
operator""_big, operator""_bigF を変更する
文字列→ BigInt / BigFloat の変換のパフォーマンスを悪くしないために、コンパイル時に処理できるリテラルでだけ対応するのもいいと思います。
おお、これはこだわる人が少ないと想定されるため優先度は高くないですが、興味深い課題です。 優先度の高い課題がある程度片付いたら本格的な検討を進めます。 何かアップデートがあればこの Issue にコメントをつなげていってください。