Concepts — 代数的コンセプト

概要

C++23 の concept を用いて代数構造(群、環、体、ベクトル空間など)を型レベルで表現する。 これにより、ジェネリックな数学アルゴリズムに「この関数は体の要素でないと動かない」「この関数はベクトル空間上の線形写像でないと動かない」 といった型制約を付けることができ、コンパイル時にエラーを検出できる。

たとえば「二次方程式の解の公式」は体 (Field) 上でのみ意味を持つ。 template<concepts::Field T> と制約を書くだけで、 double, Float, Rational, Complex<double> のいずれでも動作し、 int のような体でない型を渡すとコンパイルエラーになる。

構文チェックの限界: C++ concepts は構文的チェックのみ行う。 乗法の可換性、結合律、ヤコビ恒等式などの意味的公理はコンパイル時に検証できない。 たとえば Quaternion<double> は構文的に Field を満たすが、 乗法が非可換なので意味的には体ではない。この制約は C++ concepts の本質的な限界である。

  • 基本演算: HasBasicArithmetic, Comparable, HasMathConstants
  • 代数構造: Ring, DivisionRing, Field, OrderedField — 加法・乗法の組み合わせで定義される代数的構造
  • ベクトル空間: VectorSpace, InnerProductSpace, NormedVectorSpace, BanachSpace, HilbertSpace — 線形代数と関数解析の構造
  • 数値型: Scalar, IntegerType, RationalType, ComplexType, QuaternionType, Numeric — 具体的な数値型との対応付け
  • 行列・ベクトル・テンソル: MatrixOf, VectorOf, TensorOf — コンテナとしてのインターフェース制約
  • 高度な構造: Module, Algebra, LieAlgebra — 環上の加群、代数、リー代数

基本演算コンセプト

型が持つべき最も基本的な演算インターフェースを定義する。 これらは上位のコンセプト(Field, VectorSpace 等)のビルディングブロックである。

HasBasicArithmetic

template<typename T>
concept HasBasicArithmetic = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { a / b } -> std::convertible_to<T>;
    { -a }    -> std::convertible_to<T>;
};

四則演算と単項マイナスを持つ型。

Comparable / EqualityComparable

template<typename T>
concept Comparable = requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>;
    { a != b } -> std::convertible_to<bool>;
    { a <  b } -> std::convertible_to<bool>;
    { a <= b } -> std::convertible_to<bool>;
    { a >  b } -> std::convertible_to<bool>;
    { a >= b } -> std::convertible_to<bool>;
};

template<typename T>
concept EqualityComparable = requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>;
    { a != b } -> std::convertible_to<bool>;
};
コンセプト要件
Comparable全順序(==, !=, <, <=, >, >=
EqualityComparable等値比較のみ(==, !=

HasMathConstants

template<typename T>
concept HasMathConstants = requires {
    { T::pi() } -> std::convertible_to<T>;
    { T::e() }  -> std::convertible_to<T>;
};

数学定数($\pi$, $e$ など)にアクセス可能な型。

代数構造コンセプト

代数構造とは、集合とその上で定義された演算の組み合わせである。 以下の階層で、下に行くほどより多くの性質が要求される。 ライブラリの関数テンプレートは、必要最小限のコンセプトで制約されている。

代数構造の階層

以下に主要な代数構造の包含関係を示す。矢印は「追加の公理を持つ、より豊かな構造」の方向を指す。
AdditiveMonoid (+, 0) AdditiveGroup (+, −, 0) AdditiveAbelianGroup (+, −, 0, 可換) + 乗法・分配律 Ring 加法アーベル群 + 乗法モノイド + 分配律 + 可換性 + 乗法逆元 CommutativeRing (可換環) DivisionRing (除環, 斜体) + 零因子なし IntegralDomain (整域) + 乗法逆元 + 可換性 Field (可換体 = 可換な除環) + 全順序 OrderedField (順序体)

単位元の規約: 本ライブラリでは Ring は乗法単位元 $1$ を持つもの(単位的環, unital ring)として定義している。 Ring = AdditiveAbelianGroup && MultiplicativeMonoid における乗法モノイドが単位元を含むためである。 したがって CommutativeRing, IntegralDomain, DivisionRing, Field もすべて単位的である。

DivisionRing(除環, 斜体)

template<typename T>
concept DivisionRing = Ring<T> && MultiplicativeGroup<T>;

Ring に乗法逆元を加えたもの。Field と異なり乗法の可換性を要求しない。 $\text{Quaternion}\langle T\rangle$ は DivisionRing を満たすが Field は意味的に満たさない(乗法が非可換)。

C++ concepts は構文的チェックのみ行うため、交換律の違反を検出できない。 Quaternion<T> が構文的に Field も満たしてしまうのは既知の制約である。 DivisionRing を使って意図を明示することで、コードの可読性が向上する。

Field

template<typename T>
concept Field = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
    { a - b } -> std::convertible_to<T>;
    { a * b } -> std::convertible_to<T>;
    { a / b } -> std::convertible_to<T>;
    { -a }    -> std::convertible_to<T>;
    { T(0) }  -> std::convertible_to<T>;
    { T(1) }  -> std::convertible_to<T>;
};

四則演算と零元 $0$・単位元 $1$ を持つ型。 数学的には「加法と乗法がともにアーベル群(乗法は可換)をなし、分配律が成り立つ代数系」である。 直感的には「足し算・引き算・掛け算・割り算が全てできる」集合。 乗法の可換性 $ab = ba$ を要求する点が DivisionRing(除環)との違いであり、 Quaternion<T> は構文的に Field を満たすが、乗法が非可換なため意味的には体ではない。 double, float, Float, Rational, ModularInt<P> (P が素数) などが満たす。

Fieldtraits_part1.hpp で定義されており、algebraic_concepts.hpp から参照される。

OrderedField

template<typename T>
concept OrderedField = Field<T> && requires(T a, T b) {
    { a < b }  -> std::convertible_to<bool>;
    { a <= b } -> std::convertible_to<bool>;
    { a > b }  -> std::convertible_to<bool>;
    { a >= b } -> std::convertible_to<bool>;
};

Field に体演算と両立する全順序(大小比較)を加えたもの。 順序体の順序は単なる全順序ではなく、加法と乗法に対する互換性条件 $a \le b \Rightarrow a+c \le b+c$ および $0 \le a,\; 0 \le b \Rightarrow 0 \le ab$ を満たす必要がある(C++ concepts では構文的に検証不能)。 実数は順序体だが、複素数は順序体として定義できない(体の演算と両立する全順序が存在しない)ため、 Complex<T>OrderedField を満たさない。 「大小比較が必要な関数」(例: max, clamp, 区間演算)の制約に適する。

Scalar

template<typename T>
concept Scalar = Field<T> || std::integral<T>;

体または整数型。スカラー乗算の係数として使用する。 数学的にはスカラーは体の元であるべきだが、整数型 (int, int64_t 等) も 実用上スカラー倍の係数として頻繁に使われるため、実用的に緩和した定義としている。

ベクトル空間コンセプト

ベクトル空間とは、「ベクトルどうしの足し算」と「スカラー(数)とベクトルの掛け算」ができる構造である。 高校で学ぶ $\mathbb{R}^2$, $\mathbb{R}^3$ の矢印ベクトルが典型例だが、関数空間や行列の集合もベクトル空間となる。 ここでは、ベクトル空間とその上の追加構造(内積、ノルム、完備性)を段階的にコンセプトとして定義する。

VectorSpace

template<typename V, typename S>
concept VectorSpace = requires(V v, V w, S s) {
    { v + w } -> std::convertible_to<V>;
    { v - w } -> std::convertible_to<V>;
    { s * v } -> std::convertible_to<V>;
    { v * s } -> std::convertible_to<V>;
    { -v }    -> std::convertible_to<V>;
};

体 $S$ 上のベクトル空間。ベクトルどうしの加減算とスカラー倍が可能な型の組み合わせを表す。 数学的には、8 つの公理(加法の結合律・交換律・零元、スカラー乗法の結合律・分配律など)を満たす必要があるが、 C++ concepts では構文的にこれらを検証できないため、演算の存在のみをチェックする。

テンプレート引数説明
Vベクトル型
Sスカラー体

Vector<double>VectorSpace<Vector<double>, double> を満たす。

InnerProductSpace

template<typename V, typename S = double>
concept InnerProductSpace = VectorSpace<V, S> && requires(V v, V w) {
    { inner_product(v, w) } -> std::convertible_to<S>;
};

内積 $\langle v, w \rangle$ を持つベクトル空間。 内積は 2 つのベクトルからスカラーを返す関数で、「ベクトルの角度」や「射影」を定義する基盤となる。 フリー関数 inner_product(v, w) の存在を要求する。 内積からノルム $\|v\| = \sqrt{\langle v, v \rangle}$ を自然に導出できるため、 InnerProductSpaceNormedVectorSpace よりも強い制約である。

NormedVectorSpace / BanachSpace / HilbertSpace

template<typename V, typename S = double>
concept NormedVectorSpace = VectorSpace<V, S> && requires(V v) {
    { norm(v) } -> std::convertible_to<S>;
};

template<typename V, typename S = double>
concept BanachSpace = NormedVectorSpace<V, S>;  // 完備なノルム空間

template<typename V, typename S = double>
concept HilbertSpace = InnerProductSpace<V, S>;  // 完備な内積空間
コンセプト要件数学的定義
NormedVectorSpacenorm(v) が利用可能ノルム空間 — ベクトルの「大きさ」が定義される
BanachSpaceノルム空間(完備性は構文的に検証不能)Banach 空間 — 完備なノルム空間(コーシー列が収束する)
HilbertSpace内積空間(完備性は構文的に検証不能)Hilbert 空間 — 完備な内積空間(量子力学の状態空間など)

LinearMap

template<typename F, typename V1, typename V2, typename S>
concept LinearMap = VectorSpace<V1, S> && VectorSpace<V2, S> &&
    requires(F f, V1 v, V1 w, S s) {
        { f(v) }     -> std::convertible_to<V2>;
        { f(v + w) } -> std::convertible_to<V2>;  // 加法性
        { f(s * v) } -> std::convertible_to<V2>;  // 斉次性
    };

ベクトル空間 $V_1$ から $V_2$ への線形写像。 線形写像は加法性 $f(v+w) = f(v)+f(w)$ と斉次性 $f(sv) = sf(v)$ を満たす関数であり、 行列による変換はすべて線形写像である。 Foperator() を持つ関数オブジェクト(ラムダ、ファンクタなど)であればよい。

テンプレート引数説明
F写像型(関数オブジェクト)
V1定義域
V2値域
Sスカラー体

ConjugateLinearMap

template<typename F, typename V1, typename V2, typename S>
concept ConjugateLinearMap = VectorSpace<V1, S> && VectorSpace<V2, S> &&
    requires(F f, V1 v, V1 w, S s) {
        { f(v) }     -> std::convertible_to<V2>;
        { f(v + w) } -> std::convertible_to<V2>;  // 加法性
        { f(s * v) } -> std::convertible_to<V2>;  // 共役斉次性
    };

共役線形写像(反線形写像)。加法性 $f(v+w) = f(v)+f(w)$ は通常の線形写像と同じだが、 スカラー倍に対して $f(sv) = \overline{s}\,f(v)$(スカラーの共役をとる)という性質を持つ。 量子力学のブラ・ケット記法で現れる。 共役斉次性の完全な検証はコンパイル時に行えないため、構文的なマーカーとして機能する。

数値型コンセプト

上記の代数構造コンセプトは抽象的な演算の存在を制約するが、 実際のプログラムでは「この関数は有理数型に対してのみ動作する」「四元数の成分にアクセスしたい」 といった、より具体的な型の構造に依存するケースがある。 数値型コンセプトはそのような場面で使い分けるためのものである。

コンセプト説明代表的な型
Scalar体または整数型double, Rational, int
IntegerType整数型(std::integral<T> && Ring<T>int, int64_t
RationalType有理数型(構造的)Rational
FloatingPointType浮動小数点型(std::floating_point<T> && OrderedField<T>double, float
ComplexType複素数型std::complex<double>
QuaternionType四元数型(構造的)Quaternion<double>
Numeric数値型(abs, max, min 付き)算術型全般

RationalType(構造的コンセプト)

template<typename T>
concept RationalType = Field<T> && requires(T r) {
    { r.numerator() };
    { r.denominator() };
};

分子 numerator()・分母 denominator() に直接アクセスする必要がある場合に使用する。 たとえば連分数展開やエジプト分数への変換など、有理数の内部構造に依存するアルゴリズムに適する。 加減乗除だけで完結する汎用アルゴリズムでは Field / OrderedField / Scalar で制約すれば十分である。

ComplexType

template<typename T>
concept ComplexType = requires(T a) {
    typename T::value_type;
    requires FloatingPointType<typename T::value_type>;
    { std::real(a) } -> std::convertible_to<typename T::value_type>;
    { std::imag(a) } -> std::convertible_to<typename T::value_type>;
    { std::abs(a) }  -> std::convertible_to<typename T::value_type>;
    { std::arg(a) }  -> std::convertible_to<typename T::value_type>;
    { std::conj(a) } -> std::convertible_to<T>;
};

std::real / std::imag 等の標準関数に対応する複素数型。 std::complex<double> が満たす。

QuaternionType(構造的コンセプト)

template<typename T>
concept QuaternionType = requires(T q) {
    typename T::value_type;
    { q.w } -> std::convertible_to<typename T::value_type>;
    { q.x } -> std::convertible_to<typename T::value_type>;
    { q.y } -> std::convertible_to<typename T::value_type>;
    { q.z } -> std::convertible_to<typename T::value_type>;
    { q.conj() }    -> std::convertible_to<T>;
    { q.norm() }    -> std::convertible_to<typename T::value_type>;
    { q.inverse() } -> std::convertible_to<T>;
};

4 成分 ($w, x, y, z$) と共役・ノルム・逆元を要求する構造的コンセプト。 四元数は 3D 回転の表現($q v q^{-1}$)やコンピュータグラフィックスで多用される。 代数的には除環 (DivisionRing) であり、乗法が非可換である点が複素数との大きな違いである。

各型とコンセプトの対応

FieldOrderedFieldDivisionRing構造的コンセプト
int$\checkmark$*$\checkmark$*$\checkmark$*IntegerType
Rational$\checkmark$$\checkmark$$\checkmark$RationalType
double$\checkmark$$\checkmark$$\checkmark$FloatingPointType
Complex<T>$\checkmark$$\times$$\checkmark$ComplexType
Quaternion<T>$\checkmark$*$\times$$\checkmark$QuaternionType

* 構文的には満たすが、意味的には不正確(C++ concepts の既知の制約)

行列・ベクトル・テンソルコンセプト

行列やベクトルのコンテナとしてのインターフェースを制約する。 前述の VectorSpace が「代数的な演算」を要求するのに対し、 こちらは「要素アクセス」「サイズ取得」といったデータ構造としての操作を要求する。 密行列、疎行列、帯行列など異なる実装に対して統一的なアルゴリズムを書くために使用する。

MatrixOf

template<typename M, typename T>
concept MatrixOf = requires(M m, std::size_t i, std::size_t j) {
    typename M::value_type;
    { m(i, j) }  -> std::convertible_to<T>;
    { m.rows() } -> std::convertible_to<std::size_t>;
    { m.cols() } -> std::convertible_to<std::size_t>;
};

行列インターフェース: value_type の公開、要素アクセス m(i, j)、行数 rows() / 列数 cols()

テンプレート引数説明
M行列型
T要素型

VectorOf

template<typename V, typename T>
concept VectorOf = requires(V v, std::size_t i) {
    typename V::value_type;
    { v[i] }     -> std::convertible_to<T>;
    { v.size() } -> std::convertible_to<std::size_t>;
};

ベクトルインターフェース: value_type の公開、要素アクセス v[i]、サイズ size()

TensorOf

template<typename T, typename S>
concept TensorOf = requires(T t, size_t i, size_t j) {
    { t.rank() }    -> std::convertible_to<size_t>;
    { t.shape(i) }  -> std::convertible_to<size_t>;
    { t.size() }    -> std::convertible_to<size_t>;
    { t(i, j) }     -> std::convertible_to<S>;
};

テンソルインターフェース: 階数 rank() / シェイプ shape(i) / 要素数 size() / 要素アクセス。 テンソルは行列(2 階)やベクトル(1 階)の一般化であり、物理学(応力テンソル、曲率テンソルなど)や機械学習で使用される。

特殊行列コンセプト

template<typename M, typename T>
concept SquareMatrixOf = MatrixOf<M, T> && requires(M m) {
    requires m.rows() == m.cols();
    { m.is_square() } -> std::convertible_to<bool>;
};

template<typename M, typename T>
concept SymmetricMatrixOf = SquareMatrixOf<M, T>;  // マーカー

template<typename M, typename T>
concept OrthogonalMatrixOf = SquareMatrixOf<M, T>; // マーカー
コンセプト説明
SquareMatrixOf正方行列(is_square() 付き)— 行列式・逆行列・固有値など正方行列に限定される操作の制約に使用
SymmetricMatrixOf対称行列(マーカー)— $A = A^T$ の検証はコンパイル時に不可能なため、実行時に別途検証が必要
OrthogonalMatrixOf直交行列(マーカー)— $M^T M = I$ の検証は実行時に行う。SO(3) の回転行列などが該当

高度な代数構造

ベクトル空間のスカラーが体 (Field) でなく環 (Ring) であるような一般化が加群 (Module) であり、 さらに「ベクトルどうしの掛け算」も備えた構造が代数 (Algebra) である。 リー代数 (Lie Algebra) は、可換でも結合的でもない特殊な積(リー括弧)を持つ代数で、 連続的な対称性(リー群)の微分的な構造を記述する。calx のリー群モジュール(SE(3), SO(3) 等)で使用される。

template<typename M, typename R>
concept Module = AdditiveAbelianGroup<M> && Ring<R> &&
    requires(M m, M n, R r, R s) {
        { r * m } -> std::convertible_to<M>;
        { m * r } -> std::convertible_to<M>;
    };

template<typename A, typename F>
concept Algebra = VectorSpace<A, F> && Ring<A> &&
    requires(A a, A b, F f) {
        { f * (a * b) } -> std::convertible_to<A>;  // 双線形性の一部
    };

template<typename A, typename F>
concept AssociativeAlgebra = Algebra<A, F>;

template<typename L, typename F>
concept LieAlgebra = VectorSpace<L, F> && requires(L a, L b) {
    { a * b } -> std::convertible_to<L>;  // リー積
};

template<typename V, typename S>
concept FiniteDimensionalVectorSpace = VectorSpace<V, S> &&
    requires(V v) {
        { v.size() } -> std::convertible_to<std::size_t>;
    };
コンセプト説明
Module<M, R>加群 — 環 $R$ 上の加法群。ベクトル空間の一般化で、スカラーが体でなく環でもよい。 整数係数の格子($\mathbb{Z}$-加群)などが例
Algebra<A, F>代数 — 体 $F$ 上のベクトル空間であり、かつ環でもある構造。 「ベクトルどうしの掛け算」がスカラー倍と両立する(双線形性を部分的に検証)。 行列環 $M_n(F)$ が典型例
AssociativeAlgebra<A, F>結合代数 — 乗法が結合律 $a(bc) = (ab)c$ を満たす代数。 結合律はコンパイル時に検証不能のためマーカーとして機能
LieAlgebra<L, F>リー代数 — リー括弧 $[a, b]$ を operator* で表現する。 反可換性 $[a,b] = -[b,a]$ とヤコビ恒等式を満たすべきだが、コンパイル時検証は不可能。 calx の SE(3), SO(3) 等のリー群に対応するリー代数 $\mathfrak{se}(3)$, $\mathfrak{so}(3)$ で使用
FiniteDimensionalVectorSpace<V, S>有限次元ベクトル空間 — size() で次元数を取得可能。 関数空間のような無限次元と区別する必要がある場合に使用

反可換性やヤコビ恒等式などの代数的公理はコンパイル時に検証できないため、これらのコンセプトは構文的マーカーとして機能する。意味的な正しさは型設計者の責任となる。

C++23 拡張コンセプト

C++23 準拠のコンパイラ(__cplusplus >= 202302L)でのみ利用可能な追加コンセプト。 Field を「実数的」「複素数的」に細分化したものや、疎行列・帯行列のような特殊なデータ構造のコンセプトが含まれる。

コンセプト説明
RealFieldOrderedField + sqrt, abs, powdouble, Float など実数体向け。 三角関数や指数関数を使うアルゴリズムの制約に適する
ComplexFieldField + real, imag, abs, arg, conjstd::complex<double> など複素数体向け。 FFT やスペクトル解析などの制約に適する
RealVectorSpace<V, S>$S$ が RealField であるベクトル空間。実数係数の線形代数アルゴリズム向け
ComplexVectorSpace<V, S>$S$ が ComplexField であるベクトル空間。エルミート内積を使うアルゴリズム向け
SparseMatrixOf<M, T>疎行列 — non_zeros() で非ゼロ要素数を取得可能。大規模疎行列ソルバーの制約に使用
BandMatrixOf<M, T>帯行列 — lower_bandwidth(), upper_bandwidth() 付き。三重対角行列などの効率的な解法で使用

状態管理コンセプト

calx の多倍長型 (Int, Float) は IEEE 754 と同様に NaN, Infinity などの特殊状態を伝播する。 状態管理コンセプトは、これらの特殊状態を検査・ハンドリングできる型を制約する。 組み込み型 (double 等) はこれらのコンセプトを満たさないため、 状態管理が不要な汎用アルゴリズムでは FieldScalar で制約するのが望ましい。

コンセプト説明
HasNumericStateNaN / Infinity / 正常の判定 — isNormal(), isNaN(), isInfinite(), getState() を要求。 計算結果が有効かどうかのチェックに使用
HasDivergenceHandling発散検出 — isNotConverged(), getDivergenceDetail() を要求。 反復法(Newton 法など)の収束判定に使用
HasOverflowDetectionオーバーフロー検出 — isOverflow() を要求。 固定精度演算でのオーバーフロー検査に使用

FloatInt など calx の多倍長型はこれらを満たす。状態管理コンセプトは traits_part1.hpp で定義されている。

各コンセプトに対応する *WithStateManagement バリアントも提供されており、状態管理付きの型制約に使用できる:

template<typename T>
concept ScalarWithStateManagement = Scalar<T> && HasNumericState<T>;

template<typename T>
concept NumericWithState = Numeric<T> && HasNumericState<T>;

// 同様に InnerProductSpaceWithStateManagement,
// NormedVectorSpaceWithStateManagement, LinearMapWithStateManagement,
// ModuleWithStateManagement, AlgebraWithStateManagement 等が存在する。

使用例

以下の例では、コンセプトの使い分けを示す。 一般原則として、必要最小限のコンセプトで制約するのが望ましい。 たとえば四則演算だけ使うなら Field、大小比較も必要なら OrderedField、 四元数の成分にアクセスするなら QuaternionType を選ぶ。

#include <math/concepts/algebraic_concepts.hpp>
using namespace calx;

// ── Field: 四則演算のみ必要 ──
// double, Float, Rational, Complex<double> など全て動作する
template<concepts::Field T>
T quadratic_formula(T a, T b, T c) {
    return (-b + sqrt(b * b - T(4) * a * c)) / (T(2) * a);
}

// ── OrderedField: 大小比較が必要 ──
// Complex は全順序がないので使えない(コンパイルエラー)
template<concepts::OrderedField T>
T clamp(T x, T lo, T hi) {
    if (x < lo) return lo;
    if (x > hi) return hi;
    return x;
}

// ── DivisionRing: 非可換な乗法逆元が必要 ──
// Quaternion でも安全に動作する
template<concepts::DivisionRing T>
T normalize(const T& q) {
    return q * q.inverse();  // 非可換でも安全
}

// ── QuaternionType: 四元数の内部構造にアクセス ──
template<concepts::QuaternionType Q>
auto scalar_part(const Q& q) {
    return q.w;  // w, x, y, z 成分に直接アクセス
}

// ── RationalType: 有理数の内部構造にアクセス ──
template<concepts::RationalType R>
bool is_unit_fraction(const R& r) {
    return r.numerator() == 1;  // 分子が 1(単位分数)
}

// ── VectorSpace: ベクトル空間上の汎用アルゴリズム ──
template<typename V, concepts::Field S>
    requires concepts::VectorSpace<V, S>
V midpoint(const V& a, const V& b) {
    return (a + b) * S(0.5);
}

// ── HasNumericState: calx 多倍長型の特殊状態を検査 ──
template<concepts::HasNumericState T>
bool is_valid_result(const T& x) {
    return x.isNormal();  // NaN でも Infinity でもない
}