An additive strong typedef library for C++14/17/20
An additive strong typedef library for C++14/17/20 using the Boost Software License 1.0
Intro
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/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<>.
The header file <strong_type/strong_type.hpp> brings you all functionality.
There are more fine-grained headers available, which may speed up builds in
some situations.
A strong type can be used as an NTTP (Non Type Template Parameter), if the underlying type can be, for compilers and standards that support it.
Modifiers:
-
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.Available in
strong_type//affine_point.hpp. -
strong::arithmeticallows addition, subtraction, multiplication, division and remainder of instances.std::numeric_limits<T>is specialized for types using thestrong::arithmeticmodifier.Available in
strong_type/arithmetic.hpp. -
strong::bicrementable. Obviously a made up word for the occation. Implements bothstrong::incrementableandstrong::decrementable.Available in
strong_type/bicrementable.hpp -
strong::bitarithmeticallows bitwise&, bitwise|, bitwise^and shift operations.Available in
strong_type/bitarithmetic.hpp. -
strong::booleanprovidesexplicit operator bool() const, providing the underlying type supports it.Available in
strong_type/boolean.hpp. -
strong::convertible_to<Ts...>provides anexplicit operator Ts() constfor each typeTs, providing the underlying type supports it.Available in
strong_type/convertible_to.hpp. -
strong::decrementable. Providesoperator--for the strong type, using the operator of the underlying type.Available in
strong_type/incrementable.hpp -
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.Available in
strong_type/type.hpp -
strong::differenceallows instances to be subtracted and added (yielding astrong::difference).Conditionally, if the underlying type supports it,
strong_differenceis ordered, may be divided (yielding the base type), or multiplied or divided with the base type, yielding anotherstrong::difference. Also, conditionally, 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::equality.Available in
strong_type/difference.hpp. -
strong::equalityprovides operators==and!=. The strong type can be compared for equality or inequality.Available in
strong_type/equality.hpp. -
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.Available in
strong_type/equality_with.hpp. -
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.fmt::formatallows formatting also types that arestrong::ostreamable.Available in
strong_type/formattable.hpp. -
strong::hashableallowsstd::hash<>on the type (forwards to the underlying type,) to allow use instd::unordered_set<>andstd::unordered_map<>.Available in
strong_type/hashable.hpp. -
strong::implicitly_convertible_to<Ts...>provides anoperator Ts() constfor each typeTs, providing the underlying type supports it.Available in
strong_type/implicitly_convertible_to.hpp. -
strong::incrementable. Provides `operator++ for the strong type, using the operator of the underlying type.Available in
strong_type/incrementable.hpp -
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.Available in
strong_type/indexed.hpp. -
strong::iostreamable. Bothstrong::istreamableandstrong::ostreamable.Available in
strong_type/iostreamable.hpp -
strong::istreamable. Provides the defaultistreamextractionoperator>>for the strong type, as handled by the underfying type. Provide your own operator istead if you prefer a custom istream extraction operator.Available in
strong_type/istreamable.hpp -
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.The type trait
std::iterator_traitsmirrors the traits of the underlying iterator type.Available in
strong_type/iterator.hpp -
strong::orderedprovides operators '<', '<=', '>=' and '>'. The strong type offers the same ordering relatin as the underlying type.Available in
strong_type/ordered.hpp -
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.Available in
strong_type/ordered_with.hpp -
strong::ostreamable. Provides the defaultostreaminsertionoperator<<for the strong type, as handled by the underlying type. Provide your own operator instead if you prefer a custom ostream insertion operator.Available in
strong_type/ostreamable.hpp -
strong::partially_orderedprovides operator '<=>' The strong type offers the same ordering relatin as the underlying type. The result isstd::partial_ordering. Note! This does not imply ´strong::equality´.Available in
strong_type/ordered.hpp -
strong::partially_ordered_with<Ts...>provides operator '<=>' 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::partially_orderedfor that. The result isstd::partial_ordering. Note! This does not imply ´strong::equality_with<Ts...>´.Available in
strong_type/ordered_with.hpp -
strong::pointerallowsoperator*andoperator->, and comparisons withnullptrproviding the underlying type supports it.Available in
strong_type/pointer.hpp -
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.Available in
strong_type/range.hpp -
strong::regular. Same assemiregularand also equality comparable. A good default base for most types.Available in
strong_type/regular.hpp -
strong::scalable_with<Ts...>Allows multiplying and dividing the value with each typeTs, providing the underlying type supports it. It also allows dividing instances ofscalable_with<>, if the underlying type supports it, and returns the first type in the list ofTs....Available in
strong_type/scalable_with.hpp -
strong::semiregular. This gives you default constructible, move/copy constructible, move/copy assignable and swappable. A decent default for many types.Available in
strong_type/semiregular.hpp. -
strong::strongly_orderedprovides operator '<=>' The strong type offers the same ordering relatin as the underlying type. The result isstd::strong_ordering. Note! This does not imply ´strong::equality<Ts...>´.Available in
strong_type/ordered.hpp -
strong::strongly_ordered_with<Ts...>provides operator '<=>' 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::strongly_orderedfor that. The result isstd::strong_orderingNote! This does not imply ´strong::equality_with<Ts...>´.Available in
strong_type/ordered_with.hpp -
strong::unique. Make the type move constructible and move assignable but not copy constructible nor copy assignable.Available in
strong_type/unique.hpp -
strong::weakly_orderedprovides operator '<=>' The strong type offers the same ordering relatin as the underlying type. The result isstd::weak_ordering. Note! This does not imply ´strong::equality´.Available in
strong_type/ordered.hpp -
strong::weakly_ordered_with<Ts...>provides operator '<=>' 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::weakly_orderedfor that. The result isstd::weak_orderingNote! This does not imply ´strong::equality_with<Ts...>´.Available in
strong_type/ordered_with.hpp
Utilities:
A number of small utilities are available directly in strong_type/type.hpp.
-
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 } -
strong::type_is<type, modifier>, a boolean constant type whith the value ofstrong::type_is_v<type, modifier>. -
strong::type_is_v<type, modifier>is a constexpr predicate to test if a type has a modifier. For variadic modifiers, likestrong::ordered_with<Ts...>, it tests each of the typesTsindividually. Example:using handle = strong::type<int, struct handle_, strong::regular>; static_assert(strong::type_is_v<handle, strong::equality>); static_assert(strong::type_is_v<handle, strong::regular>); using id = strong::type<int, struct id_, strong::ordered_with<int, long>>; static_assert(strong::type_is_v<id, strong::ordered_with<int, long>>); static_assert(strong::type_is_v<id, strong::ordered_with<long>>); static_assert(strong::type_is_v<id, strong::ordered_with<int>>); static_assert(strong::type_is_v<id, strong::ordered_with<>>);All
static_asserts above pass.
Writing a modifier
A modifier is a nested structure. The outer type, a struct or class, is what
the user sees. Inside it is a struct/class template that is a
CRTP mixin, and
it must be named modifier, and the type it will be instantiated with is the
complete strong type. A type
using my_strong_type = strong::type<int, struct my_, my_modifier> will inherit
publically from my_modifier::modifier<my_strong_type>. This CRTP mixin
implements the functionality of the modifier.
As an example, let's make a modifier that uses one value from the value space
to mean 'has no value'. It is not uncommon in some low level code to see
and int being used, and the value -1 to mean no value. We can call it
optional<N>, where N is the 'has no value' value, and the interface mimics
that of std::optional.
template <auto no_value>
struct optional
{
template <typename T>
struct modifier
{
// implementation here
};
};
This can already be used, but it's not very useful yet:
using my_type = strong::type<int, struct tag_, optional<0>>;
static_assert(strong::type_is_v<my_type, optional<0>);
Let's add some functionality to the mixin. Since the strong type inherits
publically from the modifier<> template, any public member function declared
here becomes available from the strong type itself.
template <auto no_value>
struct optional
{
template <typename T> // This is the strong type
struct modifier
{
constexpr bool has_value() const noexcept
{
auto& self = static_cast<const T&>(*this);
return value_of(self) != no_value;
}
};
};
Since the modifier mixin inherits from the strong type, it is always safe to
static_cast<> to the
strong type.
It is now possible to query your strong type if it has a value or not.
using my_type = strong::type<int, struct tag_, optional<0>>;
static_assert(my_type{3}.has_value());
stacic_assert(! my_type{0}.has_value());
std::optional<> also has operator* to get the underlying value, without
checking if it's valid. Let's add that too.
template <auto no_value>
struct optional
{
template <typename T> // This is the strong type
struct modifier
{
constexpr bool has_value() const noexcept;
constexpr strong::underlying_type_t<T>& operator*() noexcept
{
auto& self = static_cast<T&>(*this);
return value_of(self);
}
constexpr const strong::underlying_type_t<T>& operator*() const noexcept
{
auto& self = static_cast<const T&>(*this);
return value_of(self);
}
};
};
If you want to move out of r-values, you need special overloads for that too,
which unfortunately makes the code quite repetitive. Writing the operators as
friend functions, taking the T as a parameter removes the need for the casts.
template <auto no_value>
struct optional
{
template <typename T> // This is the strong type
struct modifier
{
constexpr bool has_value() const noexcept;
friend constexpr decltype(auto) operator*(T& self) noexcept
{
return value_of(self);
}
friend constexpr decltype(auto) operator*(const T& self) noexcept
{
return value_of(self);
}
friend constexpr decltype(auto) operator*(T&& self) noexcept
{
return value_of(std::move(self));
}
friend constexpr decltype(auto) operator*(const T&& self) noexcept
{
return value_of(std::move(self));
}
};
};
std::optional<> also has member functions .value(), which returns the value
if there is one, or throws.
template <auto no_value>
struct optional
{
template <typename T> // This is the strong type
struct modifier
{
constexpr bool has_value() const noexcept;
constexpr friend decltype(auto) operator*(T& t) noexcept;
constexpr friend decltype(auto) operator*(T&& t) noexcept;
constexpr friend decltype(auto) operator*(const T& t) noexcept;
constexpr friend decltype(auto) operator*(const T&& t) noexcept;
strong::underlying_type_t<T>& value()
{
if (!has_value() {
throw std::bad_optional_access();
}
auto& self = static_cast<cT&>(*this);
return value_of(self);
}
const strong::underlying_type_t<T>& value() const
{
if (!has_value() {
throw std::bad_optional_access();
}
auto& self = static_cast<cconst T&>(*this);
return value_of(self);
}
// ... and more
};
};
Unfortunately there is little that can be done to reduce the repetition. A bit can be done by writing a static helper function template:
template <auto no_value>
struct optional
{
template <typename T> // This is the strong type
struct modifier
{
constexpr bool has_value() const noexcept;
constexpr friend decltype(auto) operator*(T& t) noexcept;
constexpr friend decltype(auto) operator*(T&& t) noexcept;
constexpr friend decltype(auto) operator*(const T& t) noexcept;
constexpr friend decltype(auto) operator*(const T&& t) noexcept;
decltype(auto) value() &
{
return get_value(static_cast<T&>(*this));
}
decltype(auto) value() const &
{
return get_value(static_cast<const T&>(*this));
}
decltype(auto) value() &&
{
return get_value(static_cast<T&&>(*this));
}
decltype(auto) value() const &&
{
return get_value(static_cast<const T&&>(*this));
}
private:
template <typename TT>
static constexpr decltype(auto) get_value(TT&& self)
{
if (!self.has_value()) {
throw std::bad_optional_access();
}
return value_of(std::forward<TT>(self));
}
};
};
Here's the full implementation:
template <auto no_value>
struct optional
{
template <typename T>
struct modifier
{
constexpr bool has_value() const noexcept
{
auto& self = static_cast<const T&>(*this);
return value_of(self) != no_value;
}
friend constexpr decltype(auto) operator*(T&& self) noexcept
{
return value_of(std::move(self));
}
friend constexpr decltype(auto) operator*(const T&& self) noexcept
{
return value_of(std::move(self));
}
friend constexpr decltype(auto) operator*(T& self) noexcept
{
return value_of(self);
}
friend constexpr decltype(auto) operator*(const T& self) noexcept
{
return value_of(self);
}
constexpr decltype(auto) value() &
{
return get_value(static_cast<T&>(*this));
}
constexpr decltype(auto) value() const &
{
return get_value(static_cast<const T&>(*this));
}
constexpr decltype(auto) value() &&
{
return get_value(static_cast<T&&>(*this));
}
constexpr decltype(auto) value() const &&
{
return get_value(static_cast<const T&&>(*this));
}
private:
template <typename TT>
static constexpr decltype(auto) get_value(TT&& t)
{
if (!t.has_value()) {
throw std::bad_optional_access();
}
return value_of(std::forward<TT>(t));
}
};
};
Self test
To build the self-test program(s):
cmake <strong_type_dir> -DSTRONG_TYPE_UNIT_TEST=yes
cmake --build .
This will produce the test programs self_test, and conditionally also
test_fmt8 and test_fmt9, depending on which version(s) of
{fmt}
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.
| license | BSL-1.0 |
|---|---|
| project | strong_type |
| url | github.com/rollbear/strong_type |
| version | 12.0.0+1 |
|---|---|
| repository | https://pkg.cppget.org/1/stable |
| depends | 0 |
| reviews | +1 |



