Documentation: https://lkimuk.github.io/gmp/
- Overview
- Compiler Support
- Install
- Macro Metaprogramming
- Reflection Metaprogramming
- Named Operators
- Generic Design Patterns
- Compile-time Tests
- Acknowledgments
GMP is a header-only C++ metaprogramming library for compile-time programming and code generation. It provides four complementary layers:
- Macro metaprogramming: Preprocessor utilities for boolean logic, arithmetic, tuple manipulation, loops, overload selection, and code generation. This layer is available in C++11.
- Reflection metaprogramming: Compile-time utilities for fixed strings, type names, enum reflection, and aggregate member introspection. This layer requires C++20.
- Named operators: Utilities for turning callables into custom infix operators with value-category-aware operand binding. This layer requires C++20.
- Generic design patterns: Lightweight synchronization, singleton, and object-factory helpers for reusable generic infrastructure. This layer is available in C++11, with some conveniences improving under C++20.
Key Features
- Header-only design: Zero compilation required, just include and use
- Two-tier language support:
- Macro metaprogramming and generic design-pattern utilities require C++11
- Reflection metaprogramming and named-operator utilities require C++20
- Cross-platform: Compatible with GCC, Clang, and MSVC
- Zero dependencies: Pure standard C++, with no external dependencies
- Compile-time focused: Designed for compile-time evaluation and code generation
- Broad coverage: Includes macros, reflection utilities, named operators, and reusable design-pattern components
GMP provides feature sets with different compiler requirements:
Macro Metaprogramming and Generic Design Patterns
The macro metaprogramming utilities and the core design-pattern components should work correctly with C++11 compilers and later.
Reflection Metaprogramming and Named Operators
The reflection metaprogramming and named-operator utilities require C++20 and the following compiler versions or newer:
| Compiler | Minimum Version |
|---|---|
| MSVC | 19.37+ |
| GCC | 11.1+ |
| Clang | 18.1.0+ |
Try online quickly:
Compiler Explorer: https://godbolt.org/z/W156818n5
Header-only version
Copy the include folder to your build tree and include gmp/gmp.hpp.
CMake integration
Install system-wide and use CMake's find_package:
$ git clone https://github.com/lkimuk/gmp.git
$ cd gmp
$ cmake -B ./build
$ cmake --build ./build
$ cmake --install ./build # sudo on Linux/macOSfind_package(gmp 0.3.0 REQUIRED)
target_link_libraries(your_target PRIVATE gmp::gmp)- Boolean operations:
GMP_BOOL,GMP_NOT,GMP_AND,GMP_OR,GMP_XOR,GMP_IMPLIES - Arithmetic macros:
GMP_INC,GMP_DEC,GMP_ADD,GMP_SUB - Comparison macros:
GMP_EQUAL_INT,GMP_GREATER_INT,GMP_GREATER_EQUAL_INT,GMP_LESS_INT,GMP_LESS_EQUAL_INT,GMP_EQUAL_INT_INDEPENDENT - Conditional macros:
GMP_IF,GMP_IF_THEN_ELSE - Stringification:
GMP_STRINGIFY,GMP_CONCAT,GMP_CONCATS - Variadic handling:
GMP_SIZE_OF_VAARGS,GMP_GET_N,GMP_GET_FIRST_N,GMP_IS_EMPTY - Algorithms:
GMP_MAX,GMP_MIN,GMP_MINMAX,GMP_MAXMIN,GMP_SWAP - Remove trailing comma:
GMP_REMOVE_TRAILING_COMMA - Identifiers:
GMP_IDENTIFIERS
- Size & Access:
GMP_TUPLE_SIZE,GMP_GET_TUPLE - Modification:
GMP_TUPLE_APPEND,GMP_TUPLE_PREPEND,GMP_TUPLE_CONCAT - Slicing:
GMP_TUPLE_SKIP,GMP_TUPLE_TAKE - Detection:
GMP_IS_TUPLE
- Loop macros:
GMP_REPEAT,GMP_WHILE,GMP_FOR_EACH,GMP_FOR_EACH_INDEPENDENT - Overload dispatch:
GMP_OVERLOAD_INVOKE - Expansion control:
GMP_EXPAND,GMP_EVAL,GMP_DEFER - Index sequences:
GMP_MAKE_INDEX_SEQUENCE,GMP_RANGE - Namespace generation:
GMP_GENERATE_NAMESPACES_BEGIN,GMP_GENERATE_NAMESPACES_END
#include <gmp/gmp.hpp>
static_assert(GMP_BOOL(42) == 1, "Non-zero values are true");
static_assert(GMP_NOT(0) == 1, "NOT false is true");
static_assert(GMP_AND(1, 1) == 1, "Logical AND");
static_assert(GMP_OR(0, 1) == 1, "Logical OR");
static_assert(GMP_XOR(1, 0) == 1, "Logical XOR");
static_assert(GMP_INC(5) == 6, "Increment");
static_assert(GMP_DEC(8) == 7, "Decrement");
static_assert(GMP_ADD(2, 3) == 5, "Addition");
static_assert(GMP_SUB(10, 4) == 6, "Subtraction");
static_assert(GMP_EQUAL_INT(5, 5) == 1, "Equality check");
static_assert(GMP_GREATER_INT(7, 3) == 1, "Greater than");
static_assert(GMP_LESS_INT(2, 9) == 1, "Less than");
static_assert(GMP_MAX(3, 7) == 7, "Maximum value");
static_assert(GMP_MIN(10, 2) == 2, "Minimum value");
GMP_IF(1, expr) // expands to: expr
GMP_IF_THEN_ELSE(0, expr1, expr2) // expands to: expr2
GMP_SIZE_OF_VAARGS(a, b, c) // expands to: 3
GMP_GET_N(1, a, b, c) // expands to: b
GMP_IS_EMPTY() // expands to: 1
GMP_IS_TUPLE((a, b, c)) // expands to: 1
GMP_MINMAX(7, 3) // expands to: (3, 7)
GMP_MAXMIN(7, 3) // expands to: (7, 3)
GMP_SWAP(x, y) // expands to: y, xGMP_TUPLE_SIZE((a, b, c, d, e)) // expands to: 5
GMP_TUPLE_TAKE(2, (a, b, c, d, e)) // expands to: (a, b)
GMP_TUPLE_SKIP(2, (a, b, c, d, e)) // expands to: (c, d, e)
GMP_TUPLE_APPEND((a, b, c), d) // expands to: (a, b, c, d)
GMP_TUPLE_PREPEND((b, c, d), a) // expands to: (a, b, c, d)
GMP_TUPLE_CONCAT((a, b), (c, d)) // expands to: (a, b, c, d)
GMP_GET_TUPLE(1, (42, "hello", 3.14)) // expands to: "hello"#define PRINT(x) std::cout << x << " ";
GMP_FOR_EACH(PRINT, 1, 2, 3)
#define OVERLOAD_FUNCTION_0 "OVERLOAD_FUNCTION_0"
#define OVERLOAD_FUNCTION_X_Y "OVERLOAD_FUNCTION_X_Y"
GMP_OVERLOAD_INVOKE(OVERLOAD_FUNCTION, 0) // expands to: "OVERLOAD_FUNCTION_0"
GMP_OVERLOAD_INVOKE(OVERLOAD_FUNCTION, X, Y) // expands to: "OVERLOAD_FUNCTION_X_Y"
GMP_MAKE_INDEX_SEQUENCE(5) // expands to: 0, 1, 2, 3, 4
GMP_RANGE(5, 10) // expands to: 5, 6, 7, 8, 9
#define MYLIB_NAMESPACE_BEGIN GMP_GENERATE_NAMESPACES_BEGIN(mylib, parser)
#define MYLIB_NAMESPACE_END GMP_GENERATE_NAMESPACES_END(mylib, parser)
MYLIB_NAMESPACE_BEGIN
MYLIB_NAMESPACE_ENDfixed_string: A compile-time string type whose content can be manipulatedoperator+(fixed_string, fixed_string): Concatenates twofixed_stringvaluesoperator""_fs: Createsfixed_stringvalues from string literalsto_fixed_string_v<N>: Converts an integer tofixed_stringat compile timetype_name<T>(): Returns the compiler-extracted name ofTat compile timepretty_type_name<T>()(): Returns a more readable type name for supported standard typesremove_all<Values...>(constant_arg_t<fixed_string>): Removes all occurrences ofValues...from afixed_string
constant_arg_t<V>: Statically enforces the type of an NTTP compile-time argumentconstant_arg<V>: Statically enforces thatVis a compile-time valueany: A type implicitly convertible to any typeas_value<T>(): Defines a compile-time value of typeT
enum_count<E>(): Returns the number of enumerators in an enumeration type at compile timeenum_name<V>(): Returns the name of an enumerator at compile timeenum_names<E>(): Returns all enumerator names of an enumeration type at compile timeenum_values<E>(): Returns all enumerator values of an enumeration type at compile timeenum_entries<E>(): Returns all enumerator(value, name)pairs at compile timeenum_index(value): Returns the index of an enumerator withinenum_values<E>()enum_cast<E>(name): Converts an enumerator name tostd::optional<E>GMP_ENUM_RANGE(Enum, Min, Max): Customizes the scanned range for enum reflectionGMP_ENUM_VALUES(Enum, ...): Supplies explicit enumerator values for enum reflection
member_count<T>(): Returns the number of members in an aggregate type at compile timemember_name<I, T>(): Returns the name of theI-th member of a type at compile timemember_names<T>(): Returns the names of all members of a type at compile timemember_type_t<I, T>: Returns the type of theI-th member of an aggregate typemember_type_names<T>(): Returns readable member type names of an aggregate typemember_ref<I>(obj): Returns a reference to theI-th member of an aggregate objectfor_each_member(obj, fn): Visits each member of an aggregate object together with its nametype_size<T>(): Returns the aggregate payload size excluding padding
#include <array>
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
#include <gmp/gmp.hpp>
struct Person {
std::string name;
int age;
std::array<int, 3> scores;
};
enum class Color { Red, Green, Blue };
int main() {
constexpr auto type = gmp::type_name<std::vector<std::string>>();
constexpr auto pretty = gmp::pretty_type_name<std::vector<std::string>>()();
constexpr auto member_names = gmp::member_names<Person>();
constexpr auto member_types = gmp::member_type_names<Person>();
constexpr auto colors = gmp::enum_entries<Color>();
static_assert(gmp::member_count<Person>() == 3);
static_assert(std::is_same_v<gmp::member_type_t<1, Person>, int>);
static_assert(gmp::type_size<Person>() == sizeof(std::string) + sizeof(int) + sizeof(std::array<int, 3>));
std::cout << "== Type Info ==\n";
std::cout << "raw_type : " << type << "\n";
std::cout << "pretty_type : " << pretty << "\n";
std::cout << "first_member : " << member_names[0] << " -> " << member_types[0] << "\n";
std::cout << "\n== Enum Entries ==\n";
for (const auto& [value, name] : colors) {
std::cout << static_cast<int>(value) << " : " << name << "\n";
}
Person person{ "Miles", 28, {95, 88, 91} };
std::cout << "\n== Person Members ==\n";
gmp::for_each_member(person, [](auto&& member_name, auto&& member_value) {
using member_t = std::remove_cvref_t<decltype(member_value)>;
if constexpr (std::is_same_v<member_t, std::array<int, 3>>) {
std::cout << member_name << ": [";
for (std::size_t i = 0; i < member_value.size(); ++i) {
if (i != 0) {
std::cout << ", ";
}
std::cout << member_value[i];
}
std::cout << "]\n";
} else {
std::cout << member_name << ": " << member_value << "\n";
}
});
gmp::member_ref<1>(person) = 29;
auto color = gmp::enum_cast<Color>("Green");
std::cout << std::boolalpha;
std::cout << "\n== Mutations ==\n";
std::cout << "updated_age : " << person.age << "\n";
std::cout << "enum_cast_success : " << color.has_value() << "\n";
}On MSVC, the output is:
== Type Info ==
raw_type : std::vector<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::allocator<std::basic_string<char,std::char_traits<char>,std::allocator<char> > > >
pretty_type : std::vector<std::string>
first_member : name -> std::string
== Enum Entries ==
0 : Red
1 : Green
2 : Blue
== Person Members ==
name: Miles
age: 28
scores: [95, 88, 91]
== Mutations ==
updated_age : 29
enum_cast_success : true- Expressive operator-like DSL syntax: Enables playful and readable forms such as
^_^,<_>,+_+,*_*,-_-,/_/,%_%,&_&,|_|, and^o^ make_named_operator(...): Creates a token that turns a callable into a named operatorGMP_GENERATE_NAMED_OPERATOR_PAIRS(...): Generates named-operator pairs from explicit operator tuplesGMP_GENERATE_NAMED_OPERATOR_IDENTICAL_PAIRS(...): Generates named-operator pairs that use the same token on both sidesGMP_DISABLE_DEFAULT_NAMED_OPERATORS: Disables the default predefined named-operator pairs so custom sets can be supplied- Value-category-aware binding: Preserves lvalue/rvalue semantics for operands during operator composition
- Default operator forms:
lhs ^op^ rhs,lhs <op> rhs,lhs +op+ rhs,lhs *op* rhs,lhs -op- rhs,lhs /op/ rhs,lhs %op% rhs,lhs &op& rhs, andlhs |op| rhs - Expression-style examples:
a ^_^ b,a <_> b,a +_+ b,a *_* b,a -_- b,a /_/ b,a %_% b,a &_& b,a |_| b,a *_^ b, anda ^o^ b
#include <iostream>
#include <memory>
#include <gmp/gmp.hpp>
int main() {
int a = 10;
int b = 3;
auto _ = gmp::make_named_operator([](int x, int y) {
return x + y;
});
auto o = gmp::make_named_operator([](int x, int y) {
return x * y;
});
auto consume = gmp::make_named_operator([](std::unique_ptr<int> p, int n) {
return *p + n;
});
assert((a ^_^ b) == 13);
assert((a <_> b) == 13);
assert((a +_+ b) == 13);
assert((a *_* b) == 13);
assert((a -_- b) == 13);
assert((a /_/ b) == 13);
assert((a %_% b) == 13);
assert((a &_& b) == 13);
assert((a |_| b) == 13);
assert((a *_^ b) == 13);
assert((a ^o^ b) == 30);
assert((std::make_unique<int>(40) ^consume^ 2) == 42);
}singleton<T, LongLifeTime>: A CRTP-based singleton utility with optional long-lifetime supportobject_factory<AbstractProduct, ConstructorArgs...>: A singleton-based object factory for runtime registration and creationGMP_DISABLE_CONSTRUCTION(Class): Disables direct construction forsingleton-derived typesGMP_FACTORY_REGISTER(...): Registers product types withobject_factory
#include <iostream>
#include <memory>
#include <string>
#include <gmp/gmp.hpp>
struct logger : gmp::singleton<logger> {
void write(const std::string& message) const {
std::cout << message << "\n";
}
GMP_DISABLE_CONSTRUCTION(logger)
};
struct shape {
virtual ~shape() = default;
virtual const char* name() const = 0;
};
struct circle : shape {
const char* name() const override { return "circle"; }
};
struct square : shape {
const char* name() const override { return "square"; }
};
GMP_FACTORY_REGISTER(shape, (), circle, square)
int main() {
logger::instance().write("hello from singleton");
auto product = gmp::object_factory<shape>::instance().create_unique("circle");
std::cout << product->name() << "\n";
}The library includes compile-time-oriented tests using static_assert and build-time validation:
cmake -B ./build -DBUILD_TESTS=ON
cmake --build ./buildInspired by Boost.Preprocessor and other metaprogramming libraries.
GMP is licensed under the MIT license, see LICENSE for more information.