strong_type
strong_type copied to clipboard
An additive strong typedef library for C++14/17/20
strong_type
An additive strong typedef library for C++14/17/20 using the Boost Software License 1.0
Very much inspired by @foonathan's
type_safe library, but aim is
slightly different. Limit scope for type safety only. No runtime checks. Also
strive for a higher level abstraction of the needed functionality. The idea
is to suffer no runtime penalty, but to capture misuse at compile time
(for example accidentally subtracting from a handle, or swapping two parameters
in a function call) while still being easy to use for inexperienced
programmers.
Example use:
#include <strong_type.hpp>
using myint = strong::type<int, struct my_int_>;
myint is a very basic handle. You can initialize it. You can do
equal/not-equal comparison with other instances of the same type, and you can
access its underlying int instance with value_of(variable).
To get the underlying type of a strong type, use
typename strong::underlying_type<mytype>::type, or the convenience alias
strong::underlying_type_t<mytype>. If mytype is not a strong::type,
they give mytype.
using otherint = strong::type<int, struct other_int_>;
otherint is a distinct type from myint. If a function takes an argument of
type myint, you can't pass it an instance of otherint, and vice-versa. You
also can't cross-assign, cross-create or cross-compare.
To access more functionality, you add modifiers. For example:
using ordered_int = strong::type<int, struct ordered_int_, strong::ordered>;
Type ordered_int now supports relational order comparisons, like <,
(provided the underlying type, int this case int, does.) Type ordered_int
can thus be used as key in std::map<> or std::set<>.
Modifiers are:
-
strong::default_constructible. The strong type is not default constructible by default. This modifier enables a default constructor which uses a default constructor of the underlying type. -
strong::equalityprovides operators==and!=. The strong type can be then compared for equality or inequality. -
strong::equality_with<Ts...>provides operators==and!=between the strong type and each of the typesTs.... Note! WhileTscan include other strong types, it can not refer to the strong type being defined. Usestrong::equalityfor that. -
strong::orderedprovides operators '<', '<=', '>=' and '>'. The strong type offers the same ordering relatin as the underlying type. -
strong::ordered_with<Ts...>provides operators '<', '<=', '>=' and '>' between the strong type and each of the typesTs.... Note! WhileTscan include other strong types, it cannot refer to the strong type being defined. Usestrong::orderedfor that. -
strong::semiregular. This gives you default constructible, move/copy constructible, move/copy assignable and swappable. A decent default for many types. -
strong::regular. Same assemiregularand also equality comparable. A good default base for most types. -
strong::unique. Make the type move constructible and move assignable but not copy constructible nor copy assignable. -
strong::ostreamable,strong::istreamable,strong::iostreamable, which provide the default iostream integrations (as handled by the underlying type.) Provide your own operators instead if you prefer that. -
strong::incrementable,strong::decrementable,strong::bicrementable. Supportoperator++andoperator--. bicrementable is obviously a made- up word for the occasion, but I think its meaning is clear. -
strong::booleanprovidesexplicit operator bool() const, providing the underlying type supports it. -
strong::convertible_to<Ts...>provides anexplicit operator Ts() constfor each typeTs, providing the underlying type supports it. -
strong::implicitly_convertible_to<Ts...>provides anoperator Ts() constfor each typeTs, providing the underlying type supports it. -
strong::hashableallowsstd::hash<>on the type (forwards to the underlying type,) to allow use instd::unordered_set<>andstd::unordered_map<> -
strong::differenceallows instances to be subtracted and added (yielding astrong::difference,) divided (yielding the base type), or multiplied or divided with the base type, yielding anotherstrong::difference, and if the underlying type supports it, the remainder after division of two differences yields the underlying type, and the remainder after division of a difference and the underlying type yields a difference. Astrong::differenceis alsostrong::orderedandstrong::equality -
strong::affine_point<D>allows instances to be subtracted (yielding aD) or to add or subtract aDto an instance. See Affine Space. Examples of one dimentional affine points are pointer (withDbeingptrdiff_t,) orstd::time_point<>(withstd::duration<>asD.) An example of a multidimensional affine point is a coordinate (with a vector type forD.)Dcan be defaulted, usingstrong::affine_point<>, in which case the difference type shares the same tag. The difference type from astrong::affine_point<D>can be obtained usingtype::difference, regardless of whetherDis explicit or defaulted. It is natural thatDis of astrong::differencetype. This is a good name from a mathematical point of view, but perhaps a bit too academic, and not well aligned with the other names. -
strong::pointerallowsoperator*andoperator->, and comparisons withnullptrproviding the underlying type supports it. -
strong::arithmeticallows addition, subtraction, multiplication, division and remainder of instances. -
strong::bitarithmeticallows bitwise&, bitwise|, bitwise^and shift operations. -
strong::indexed<D>allows use of the subscript operator[] on typeD. This also allows member functionat(D), providing the underlying type supports it. A lame versionindexed<>allows subscript on any type that works. -
strong::iteratoradds functionality needed depending on iterator category. If the iterator type is arandom_access_iterator, the strong type isstrong::indexed<>andstrong::affine_point<difference>. It should be possible to specify the index type and affine_point type. -
strong::rangeadds the functionality needed to iterate over the elements. the iterator types are using the same tag as using in the range. Only implements typesiteratorandconst_iterator, and thus.begin(),.end(),.cbegin(),.cend(),.begin() constand.end() const. -
strong::formattableaddsstd::formatand/orfmt::formatcapability, based on availability of the formatting library. This can further be controlled (globally) with the definesSTRONG_HAS_STD_FORMATrespectivelySTRONG_HAS_FMT_FORMAT. With 0 to disable the support completly, and with 1 to force the support, disable the auto detection.
For modifier strong::arithmetic, the type trait std::is_arithmetic<> is true.
For modifier strong::iterator, the type trait std::iterator_traits mirrors
the traits of the underlying iterator type.
Miscellaneous:
-
strong::typeprovides a non-memberswap()function as a friend, which swaps underlying values using. -
strong::underlying_type<Type>isTforstrong::type<T, Tag, Ms...>and public descendants, andTypefor other types. -
strong::uninitializedcan be used to construct instances ofstrong::type<T...>without initializing the value. This is only possible if the underlying type istrivially default constructible, for example:void init(int*); void function() { strong::type<int, struct int_tag> x(strong::uninitialized); // x will have an unspecified value init(&value_of(x)); // hopefully the init() function assigns a value }
To build the self-test program:
cmake <strong_type_dir> -DCMAKE_BUILD_TYPE=Debug
cmake --build . --target self_test
N.B. Microsoft Visual Studio MSVC compiler < 19.22 does not handle constexpr
correctly. Those found to cause trouble are disabled for those versions.
Other libraries:
| Library | Author |
|---|---|
| type_safe | Jonathan Müller |
| NamedType | Jonathan Boccara |
| strong_typedef | Anthony Williams (justsoftwaresolutions) |
Presentations about defining and using strong types
![]() |
Jonathan Boccara from MeetingC++ 2017 |
![]() |
Barney Dellar from C++OnSea 2019 |
![]() |
Björn Fahller from ACCU 2018 |
![]() |
Adi Shavit & Björn Fahller from NDC{Oslo} 2019 |
Discussions, pull-requests, flames are welcome.



