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<>
.
-
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::equality
provides 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! WhileTs
can include other strong types, it can not refer to the strong type being defined. Usestrong::equality
for that. -
strong::ordered
provides 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! WhileTs
can include other strong types, it cannot refer to the strong type being defined. Usestrong::ordered
for 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 assemiregular
and 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::boolean
providesexplicit operator bool() const
, providing the underlying type supports it. -
strong::convertible_to<Ts...>
provides anexplicit operator Ts() const
for each typeTs
, providing the underlying type supports it. -
strong::implicitly_convertible_to<Ts...>
provides anoperator Ts() const
for each typeTs
, providing the underlying type supports it. -
strong::hashable
allowsstd::hash<>
on the type (forwards to the underlying type,) to allow use instd::unordered_set<>
andstd::unordered_map<>
-
strong::difference
allows 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::difference
is alsostrong::ordered
andstrong::equality
-
strong::affine_point<D>
allows instances to be subtracted (yielding aD
) or to add or subtract aD
to an instance. See Affine Space. Examples of one dimentional affine points are pointer (withD
beingptrdiff_t
,) orstd::time_point<>
(withstd::duration<>
asD
.) An example of a multidimensional affine point is a coordinate (with a vector type forD
.)D
can 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 whetherD
is explicit or defaulted. It is natural thatD
is of astrong::difference
type. 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::pointer
allowsoperator*
andoperator->
, and comparisons withnullptr
providing the underlying type supports it. -
strong::arithmetic
allows addition, subtraction, multiplication, division and remainder of instances. -
strong::bitarithmetic
allows 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::iterator
adds 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::range
adds the functionality needed to iterate over the elements. the iterator types are using the same tag as using in the range. Only implements typesiterator
andconst_iterator
, and thus.begin()
,.end()
,.cbegin()
,.cend()
,.begin() const
and.end() const
. -
strong::formattable
addsstd::format
and/orfmt::format
capability, based on availability of the formatting library. This can further be controlled (globally) with the definesSTRONG_HAS_STD_FORMAT
respectivelySTRONG_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.
-
strong::type
provides a non-memberswap()
function as a friend, which swaps underlying values using. -
strong::underlying_type<Type>
isT
forstrong::type<T, Tag, Ms...>
and public descendants, andType
for other types. -
strong::uninitialized
can 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.
Library | Author |
---|---|
type_safe | Jonathan Müller |
NamedType | Jonathan Boccara |
strong_typedef | Anthony Williams (justsoftwaresolutions) |
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.