Concepts — Algebraic Concepts
Overview
The sangi::concepts namespace defines the major structures of abstract algebra as C++20 concepts.
Groups, rings, fields, vector spaces, Hilbert spaces, and more can be verified at compile time,
enabling explicit type constraints on template arguments.
- Compile-time type verification — clear error messages when template arguments do not satisfy algebraic requirements
- Hierarchical design — AdditiveMonoid → AdditiveGroup → Ring → Field mirrors the mathematical inclusion hierarchy
- Over 40 concepts — covering basic arithmetic, algebraic structures, vector spaces, linear maps, numeric types, matrices/tensors, and algebras/modules
- Header-only — no linking required; just
#include - C++23 extensions — additional concepts like
SparseMatrixOf,BandMatrixOf,RealField,ComplexField(C++23 mode)
Mathematical axioms (associativity, commutativity, distributivity, etc.) cannot be verified at compile time. Only syntactic requirements (operator existence, return type compatibility) are checked. Semantic correctness is the user's responsibility.
Header
#include <math/concepts/algebraic_concepts.hpp>
Header-only. No library linkage required. Internally includes <math/core/traits.hpp>.
Namespace: sangi::concepts
Concept Hierarchy
sangi's algebraic concepts mirror the mathematical inclusion hierarchy. The diagram below shows the inclusion relationships between the major structures (an arrow A → B means "A is a super-structure of B", i.e. B satisfies A).
Algebraic structure of major types
| Type | Ring | IntegralDomain | Field | OrderedField | Notes |
|---|---|---|---|---|---|
int / long long | ✓ | ✓ | ✗ | ✗ | Integral domain but not a field (no multiplicative inverse) |
double / float | ✓ | ✓ | ✓ | ✓ | Ordered field |
sangi::Float | ✓ | ✓ | ✓ | ✓ | Arbitrary-precision floating point |
sangi::Complex<double> | ✓ | ✓ | ✓ | ✗ | No total order |
sangi::Rational<T> | ✓ | ✓ | ✓ | ✓ | Rational field |
sangi::Quaternion<double> | ✓ | ✗ | ✗ | ✗ | Division ring (non-commutative; satisfies DivisionRing) |
sangi::Polynomial<double> (default) | ✓ | ✓ | ✗ | ✗ | Integral domain; not a field in general |
sangi::Matrix<double> | ✗ | ✗ | ✗ | ✗ | No operator/ |
unsigned int | ✗ | ✗ | ✗ | ✗ | Fails AdditiveGroup (additive inverse not closed) |
Basic Arithmetic Concepts
| Concept | Requirements | Examples |
|---|---|---|
HasBasicArithmetic<T> |
+, -, *, /, unary - defined |
double, float, Int, Float, Rational, Complex<double> |
Comparable<T> |
==, !=, <, <=, >, >= defined |
double, int, Int, Float, Rational |
EqualityComparable<T> |
==, != only |
Complex<double>, Quaternion<double>, all numeric types |
HasMathConstants<T> |
T::pi(), T::e() defined |
Float. float/double do not have static methods pi()/e() and therefore do not satisfy this concept. Intended for custom types such as sangi::Float |
HasBasicArithmetic<T> conformance table
| Type | Fits | Notes |
|---|---|---|
int / double / long double | ✓ | Built-in numeric types |
sangi::Float | ✓ | Arbitrary-precision floating point |
sangi::Complex<T> | ✓ | Complex numbers |
sangi::Rational<T> | ✓ | Rationals |
sangi::Quaternion<T> | ✓ | Quaternions (non-commutative, but the five operations are closed) |
sangi::Polynomial<T> | ✓ (conditional) | Default build satisfies it (returns the polynomial-division quotient). When SANGI_POLY_DIV_RATIONAL is defined, operator/ returns RationalFunction<T> and the concept is no longer satisfied. See note below |
sangi::Matrix<T> | ✗ | No operator/ |
sangi::Vector<T> | ✗ | No operator/, no operator* |
std::string | ✗ | No -, *, /, unary - |
operator/ switch)
The behaviour of sangi::Polynomial<T>::operator/ depends on a build-time macro:
- Default: returns the polynomial-division quotient (
Polynomial<T>). SatisfiesHasBasicArithmetic. - With
SANGI_POLY_DIV_RATIONALdefined: returns a rational function (RationalFunction<T>).HasBasicArithmetic<Polynomial<T>>is no longer satisfied.
Mathematically, Polynomial<T> is an integral domain (IntegralDomain) and is not a field in general, so Field<Polynomial<T>> is never satisfied regardless of the build setting.
Algebraic Structures
The following inclusion relationships hold:
$$\text{AdditiveMonoid} \subset \text{AdditiveGroup} \subset \text{AdditiveAbelianGroup}$$ $$\text{Ring} = \text{AdditiveAbelianGroup} \cap \text{MultiplicativeMonoid}$$ $$\text{Ring} \subset \text{CommutativeRing} \subset \text{IntegralDomain} \subset \text{Field} \subset \text{OrderedField}$$| Concept | Requirements | Examples |
|---|---|---|
AdditiveMonoid<T> |
a + b, T{0} (additive identity) |
int, float, double, unsigned int, Int, Float |
AdditiveGroup<T> |
AdditiveMonoid + unary -a, a - b |
int, float, double, Int, Float |
AdditiveAbelianGroup<T> |
AdditiveGroup (commutativity is a semantic marker) | int, float, double, Int, Float |
MultiplicativeMonoid<T> |
a * b, T{1} (multiplicative identity) |
int, float, double, Int, Float |
MultiplicativeGroup<T> |
MultiplicativeMonoid + T{1} / a (multiplicative inverse) |
float, double, Float, Rational, Complex<double> |
MultiplicativeAbelianGroup<T> |
MultiplicativeGroup (commutativity is a semantic marker) | float, double, Float, Rational |
Ring<T> |
AdditiveAbelianGroup + MultiplicativeMonoid | int, float, double, Int, Float, Rational |
CommutativeRing<T> |
Ring (multiplicative commutativity is a semantic marker) | int, float, double, Int, Float, Rational |
IntegralDomain<T> |
CommutativeRing (no zero divisors is a semantic marker) | int, float, double, Int, Float, Rational |
DivisionRing<T> |
Ring + MultiplicativeGroup (includes non-commutative division rings) | float, double, Quaternion<double>, Complex<double> |
Field<T> |
+, -, *, /, unary -, T(0), T(1) |
double, float, Float, Rational, Complex<double> |
OrderedField<T> |
Field + <, <=, >, >= |
double, float, Float, Rational |
Scalar<T> |
Field<T> || std::integral<T> |
int, double, Float, Rational |
Note on unsigned types:
Unsigned integer types such as unsigned int satisfy AdditiveMonoid, but the additive inverse (-a) is not mathematically closed for unsigned types. Therefore, they do not satisfy AdditiveGroup or any higher algebraic structure (Ring, Field, etc.).
About "semantic markers": Mathematical properties such as commutativity, completeness, and the absence of zero divisors cannot be verified at compile time. These concepts function as declarations of intent (markers). No runtime checks are performed; it is the user's responsibility to ensure the type satisfies the stated property.
Ring<T> conformance table
| Type | Fits | Notes |
|---|---|---|
int / long long | ✓ | Ring of integers $\mathbb{Z}$ |
double / sangi::Float | ✓ | Every field is a ring |
sangi::Rational<T> | ✓ | Rationals |
sangi::Complex<T> | ✓ | Complex numbers |
sangi::Polynomial<T> (default) | ✓ | Polynomial ring |
sangi::Quaternion<T> | ✓ | Non-commutative ring (does not satisfy CommutativeRing) |
unsigned int | ✗ | Additive inverse not closed (fails AdditiveGroup) |
sangi::Matrix<T> | ✗ | No numeric_traits specialization (shapes generally differ) |
Field<T> conformance table
| Type | Fits | Notes |
|---|---|---|
double / float / long double | ✓ | Finite-precision field |
sangi::Float | ✓ | Arbitrary-precision floating point |
sangi::Rational<T> | ✓ | Rational field $\mathbb{Q}$ |
sangi::Complex<double> | ✓ | Complex field $\mathbb{C}$ |
int / long long | ✗ | No multiplicative inverse (integral domain) |
sangi::Quaternion<T> | ✗ | Non-commutative (does satisfy DivisionRing) |
sangi::Polynomial<T> | ✗ | Integral domain, not a field (see note below) |
sangi::Matrix<T> | ✗ | No operator/ |
Polynomial and Field)
sangi::Polynomial<T> is not a field even when the coefficient type T is — the polynomial ring T[x] is an integral domain and lacks multiplicative inverses in general. Therefore Field<Polynomial<T>> is never satisfied, regardless of the build setting.
The corresponding field of fractions is sangi::RationalFunction<T>, into which Polynomial::operator/ is promoted when SANGI_POLY_DIV_RATIONAL is defined. If you need field-level operations (e.g. safe_inverse) on polynomial-like objects, use RationalFunction<T> directly.
DivisionRing<T> conformance table
| Type | Fits | Notes |
|---|---|---|
double / float / sangi::Float | ✓ | Every field is a division ring |
sangi::Complex<T> | ✓ | Complex field |
sangi::Rational<T> | ✓ | Rational field |
sangi::Quaternion<T> | ✓ | Non-commutative division ring (does not satisfy Field, but satisfies DivisionRing) |
int | ✗ | No multiplicative inverse |
sangi::Polynomial<T> | ✗ | Integral domain but not a division ring |
OrderedField<T> conformance table
| Type | Fits | Notes |
|---|---|---|
double / float / long double | ✓ | Finite-precision approximation of the reals |
sangi::Float | ✓ | Arbitrary-precision reals |
sangi::Rational<T> | ✓ | Rational field is ordered |
sangi::Complex<T> | ✗ | No natural total order on the complex numbers |
sangi::Quaternion<T> | ✗ | Non-commutative and unordered |
Vector Spaces
Vector spaces and their extensions. The scalar type S defaults to double.
Template parameters
| Parameter | Type | Description |
|---|---|---|
V | typename | Vector type |
S | typename | Scalar type (defaults to double) |
| Concept | Requirements | Examples |
|---|---|---|
VectorSpace<V, S> |
v + w, v - w, s * v, v * s, -v |
Vector<double>, std::array<double, N> |
InnerProductSpace<V, S> |
VectorSpace + inner_product(v, w) → S |
Vector<double> (when inner_product is defined) |
NormedVectorSpace<V, S> |
VectorSpace + norm(v) → S |
Vector<double> (when norm is defined) |
BanachSpace<V, S> |
NormedVectorSpace (completeness is a semantic marker — completeness cannot be verified at compile time, so this concept serves as a declaration of intent. No runtime checks are performed) | $\mathbb{R}^n$, $\ell^p$ spaces |
HilbertSpace<V, S> |
InnerProductSpace (completeness is a semantic marker — completeness cannot be verified at compile time, so this concept serves as a declaration of intent. No runtime checks are performed) | $\mathbb{R}^n$, $\ell^2$ spaces |
FiniteDimensionalVectorSpace<V, S> |
VectorSpace + v.size() → size_t |
Vector<double> |
VectorSpace<V, S> conformance table
| (V, S) | Fits | Notes |
|---|---|---|
(sangi::Vector<double>, double) | ✓ | Standard vector space |
(sangi::Vector<Complex<double>>, Complex<double>) | ✓ | Complex vector space |
(std::array<double, N>, double) | ✓ | Fixed-length vector |
(sangi::Matrix<double>, double) | ✓ | Matrices also form a vector space |
(double, double) | ✓ | One-dimensional vector space |
(sangi::Vector<double>, int) | ✗ | Return-type mismatch for int * Vector<double> |
(std::string, double) | ✗ | No scalar multiplication |
Linear Maps
Template parameters
| Parameter | Type | Description |
|---|---|---|
F | typename | Map object type (function object) |
V1 | typename | Domain vector space |
V2 | typename | Codomain vector space |
S | typename | Common scalar type (defaults to double) |
| Concept | Requirements | Description |
|---|---|---|
LinearMap<F, V1, V2, S> |
f(v) → V2, f(v + w) → V2, f(s * v) → V2 |
$f(\alpha v + \beta w) = \alpha f(v) + \beta f(w)$. V1, V2 must be VectorSpace |
ConjugateLinearMap<F, V1, V2, S> |
Same syntactic requirements as LinearMap | $f(\alpha v) = \bar{\alpha} f(v)$. Conjugate homogeneity cannot be verified at compile time |
Numeric Type Concepts
| Concept | Requirements | Examples |
|---|---|---|
Numeric<T> |
HasBasicArithmetic + std::abs, std::max, std::min |
short, int, float, double, unsigned int |
IntegerType<T> |
std::integral<T> && Ring<T>. Ring requires numeric_traits specialization |
int, long long. short does not satisfy this concept because it lacks a numeric_traits specialization. Unsigned types do not satisfy the AdditiveGroup requirement |
NonNegativeIntegerType<T> |
std::unsigned_integral<T> |
unsigned int, size_t, uint64_t, etc. Use for array indices, sizes, and counters where non-negativity is guaranteed. Does not satisfy Ring or higher because additive inverse is not closed |
FloatingPointType<T> |
OrderedField<T> && !std::integral<T> |
float, double, long double, sangi::Float |
ComplexType<T> |
T::value_type, std::real, std::imag, std::abs, std::arg, std::conj |
std::complex<double>, std::complex<float> |
QuaternionType<T> |
T::value_type, q.w, q.x, q.y, q.z, q.conj(), q.norm(), q.inverse() |
Quaternion<double>, Quaternion<float> |
RationalType<T> |
Field<T> + r.numerator(), r.denominator() |
Rational |
Matrix & Tensor Concepts
Template parameters
| Parameter | Type | Description |
|---|---|---|
M | typename | Matrix type |
V | typename | Vector type |
T / S | typename | Element type (scalar type) |
| Concept | Requirements | Description |
|---|---|---|
MatrixOf<M, T> |
M::value_type, m(i,j) → T, m.rows(), m.cols() |
Any matrix type. Defined in traits.hpp |
VectorOf<V, T> |
V::value_type, v[i] → T, v.size() |
Any vector type. Defined in traits.hpp |
SquareMatrixOf<M, T> |
MatrixOf + m.is_square() |
Square matrix |
SymmetricMatrixOf<M, T> |
SquareMatrixOf (symmetry is a semantic marker) | Symmetric matrix. Requires runtime verification |
OrthogonalMatrixOf<M, T> |
SquareMatrixOf (orthogonality is a semantic marker) | Orthogonal matrix ($M^T M = I$) |
TensorOf<T, S> |
t.rank(), t.shape(i), t.size(), t(i,j) → S |
Multi-dimensional tensor |
SparseMatrixOf<M, T> C++23 |
MatrixOf + m.non_zeros() |
Sparse matrix |
BandMatrixOf<M, T> C++23 |
MatrixOf + m.lower_bandwidth(), m.upper_bandwidth() |
Band matrix |
MatrixOf<M, T> conformance table
| (M, T) | Fits | Notes |
|---|---|---|
(sangi::Matrix<double>, double) | ✓ | Dense matrix |
(sangi::Matrix<Complex<double>>, Complex<double>) | ✓ | Complex dense matrix |
(sangi::SparseMatrix<double>, double) | ✓ | Sparse matrix; also satisfies SparseMatrixOf |
(sangi::Matrix<double>, float) | ✗ | value_type mismatch |
(sangi::Vector<double>, double) | ✗ | No operator(i,j) |
(std::vector<std::vector<double>>, double) | ✗ | No rows()/cols()/value_type |
Algebras & Modules
Template parameters
| Parameter | Type | Description |
|---|---|---|
M | typename | Module type |
R | typename | Coefficient ring type |
A / L | typename | Vector space underlying the algebra / Lie algebra |
F | typename | Coefficient field type |
| Concept | Requirements | Description |
|---|---|---|
Module<M, R> |
AdditiveAbelianGroup<M> + Ring<R> + r * m, m * r |
Module (generalization of vector space where scalars form a ring, not a field) |
Algebra<A, F> |
VectorSpace<A,F> + Ring<A> + f * (a * b) |
Algebra (vector space with multiplicative structure) |
AssociativeAlgebra<A, F> |
Algebra (associativity is a semantic marker) | Associative algebra, e.g. matrix rings |
LieAlgebra<L, F> |
VectorSpace<L,F> + a * b → L (Lie bracket) |
Lie algebra, e.g. $\mathfrak{so}(3)$, $\mathfrak{se}(3)$ |
Examples
Generic function constrained by Field
#include <math/concepts/algebraic_concepts.hpp>
using namespace sangi::concepts;
// Inverse computation for any type satisfying Field
template<Field T>
T safe_inverse(const T& x) {
if (x == T(0)) return T(0); // avoid division by zero
return T(1) / x;
}
// Works with double, Float, Rational, Complex<double>
auto inv_d = safe_inverse(3.14);
auto inv_r = safe_inverse(Rational(3, 7));
// Run output:
// inv_d = 0.318471337579618
// inv_r = 7/3
// safe_inverse(0.0) = 0 (zero guard fires)
Vector space algorithm
// Linear combination for any type satisfying VectorSpace
template<typename V, typename S>
requires VectorSpace<V, S>
V linear_combination(S a, const V& v, S b, const V& w) {
return a * v + b * w;
}
// Run output:
// v = {1,2,3}, w = {4,5,6}, a = 2.0, b = 3.0
// linear_combination = {14, 19, 24}
Polynomial evaluation over a Ring
// Horner's method for any type satisfying Ring
template<Ring T>
T horner(const std::vector<T>& coeffs, const T& x) {
T result = T{0};
for (auto it = coeffs.rbegin(); it != coeffs.rend(); ++it) {
result = result * x + *it;
}
return result;
}
// Works with int, double, Int, Float, Rational
std::vector<double> poly = {1.0, -2.0, 3.0}; // 3x^2 - 2x + 1
double val = horner(poly, 2.0); // = 9.0
// Run output: val = 9 (= 3·4 − 2·2 + 1)
Quaternion type detection
// Combining QuaternionType and DivisionRing constraints
template<typename T>
requires QuaternionType<T> && DivisionRing<T>
auto rotation_axis(const T& q) {
auto v_norm = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z);
return std::make_tuple(q.x / v_norm, q.y / v_norm, q.z / v_norm);
}
// Run output:
// Quaternion(0, 1, 2, 2) → axis = (1/3, 2/3, 2/3), |axis| = 1
Concept-based overloading
// OrderedField: field with ordering (double, Float, Rational)
template<OrderedField T>
T clamp(const T& x, const T& lo, const T& hi) {
if (x < lo) return lo;
if (x > hi) return hi;
return x;
}
// Field: includes non-orderable fields (Complex<double>)
template<Field T>
T midpoint(const T& a, const T& b) {
return (a + b) / T(2);
}
// Run output:
// clamp(2.5, 0, 1) = 1
// clamp(-0.3, 0, 1) = 0
// midpoint(1.0, 5.0) = 3
// midpoint(1+0i, 3+4i) = 2+2i
Concept membership can be verified with static_assert sanity checks.