strong_type/12.0.0+1

[brief]

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

CI Build Status codecov

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:

Utilities:

A number of small utilities are available directly in strong_type/type.hpp.

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

Strong Types for Strong Interfaces Jonathan Boccara from MeetingC++ 2017
Strong Types in C++ Barney Dellar from C++OnSea 2019
Type Safe C++? - LOL! - ;-) Björn Fahller from ACCU 2018
Curiously Coupled Types Adi Shavit & Björn Fahller from NDC{Oslo} 2019

Discussions, pull-requests, flames are welcome.

@bjorn_fahller

version 12.0.0+1
license BSL-1.0
repository https://pkg.cppget.org/1/stable
download strong_type-12.0.0+1.tar.gz
sha256 5db6f111d1693afdc1bd1ff4495c097f0470f71c4b556bd4dcb6bb5c0ba2916d
project strong_type
url github.com/rollbear/strong_type
package-url github.com/build2-packaging/strong_type

Tests

strong_type-tests == 12.0.0

Reviews

fail 0
pass 1

Builds

toolchain public-0.17.0
target x86_64-w64-mingw32
tgt config windows_10-gcc_13.2_mingw_w64
timestamp 2025-08-23 10:36:22 UTC (18:24:54 hours ago)
result error (test) | log | rebuild
toolchain public-0.17.0
target x86_64-w64-mingw32
tgt config windows_10-gcc_13.2_mingw_w64-O2
timestamp 2025-08-23 10:34:45 UTC (18:26:31 hours ago)
result error (test) | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-gcc_14-ndebug_O3
timestamp 2025-08-22 19:16:28 UTC (01 09:44:48 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-gcc_14
timestamp 2025-08-22 19:11:01 UTC (01 09:50:15 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-gcc_14-static_O3
timestamp 2025-08-22 19:07:32 UTC (01 09:53:44 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-gcc_14-O3
timestamp 2025-08-22 19:07:00 UTC (01 09:54:16 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_fedora_40-gcc_14-bindist
timestamp 2025-08-22 13:53:07 UTC (01 15:08:10 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_14-O3
timestamp 2025-08-22 11:50:23 UTC (01 17:10:54 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_14-ndebug_O3
timestamp 2025-08-22 11:49:12 UTC (01 17:12:04 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_14-static_O3
timestamp 2025-08-22 11:47:28 UTC (01 17:13:49 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_14
timestamp 2025-08-22 11:47:19 UTC (01 17:13:57 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18_libc++-O3
timestamp 2025-08-22 11:43:43 UTC (01 17:17:34 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-clang_18_llvm_msvc_17.10-O2
timestamp 2025-08-22 11:23:31 UTC (01 17:37:45 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-clang_18_llvm_msvc_17.10
timestamp 2025-08-22 11:21:52 UTC (01 17:39:24 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-clang_17_msvc_msvc_17.10
timestamp 2025-08-22 11:20:42 UTC (01 17:40:34 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.8-O2
timestamp 2025-08-22 11:19:49 UTC (01 17:41:27 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-w64-mingw32
tgt config windows_10-gcc_13.2_mingw_w64-static_O2
timestamp 2025-08-22 11:17:42 UTC (01 17:43:34 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.8
timestamp 2025-08-22 11:17:11 UTC (01 17:44:06 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.10-O2
timestamp 2025-08-22 11:16:56 UTC (01 17:44:21 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18_libc++
timestamp 2025-08-22 10:53:46 UTC (01 18:07:31 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18_libc++-static_O3
timestamp 2025-08-22 10:52:04 UTC (01 18:09:13 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18-O3
timestamp 2025-08-22 10:51:35 UTC (01 18:09:41 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18-static_O3
timestamp 2025-08-22 10:51:32 UTC (01 18:09:45 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_18
timestamp 2025-08-22 10:49:51 UTC (01 18:11:25 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_12-bindist
timestamp 2025-08-22 10:30:19 UTC (01 18:30:57 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.10
timestamp 2025-08-22 10:29:14 UTC (01 18:32:03 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.10-static_O2
timestamp 2025-08-22 10:28:34 UTC (01 18:32:42 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-clang_18_llvm_msvc_17.10-static_O2
timestamp 2025-08-22 10:28:18 UTC (01 18:32:58 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-microsoft-win32-msvc14.3
tgt config windows_10-msvc_17.8-static_O2
timestamp 2025-08-22 09:59:22 UTC (01 19:01:54 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-freebsd14.1
tgt config freebsd_14-clang_18-O3
timestamp 2025-08-22 09:54:20 UTC (01 19:06:56 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-freebsd13.3
tgt config freebsd_13-clang_17
timestamp 2025-08-22 09:54:09 UTC (01 19:07:07 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_ubuntu_24.04-gcc_13-bindist
timestamp 2025-08-22 09:51:08 UTC (01 19:10:08 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_17_libc++
timestamp 2025-08-22 09:49:57 UTC (01 19:11:19 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-freebsd14.1
tgt config freebsd_14-clang_18
timestamp 2025-08-22 09:43:02 UTC (01 19:18:15 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_17
timestamp 2025-08-22 09:40:47 UTC (01 19:20:29 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-gcc_13.1
timestamp 2025-08-22 09:38:07 UTC (01 19:23:09 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-freebsd14.1
tgt config freebsd_14-clang_18-static_O3
timestamp 2025-08-22 09:36:26 UTC (01 19:24:50 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_17_libc++
timestamp 2025-08-21 12:22:59 UTC (02 16:38:18 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-clang_17
timestamp 2025-08-21 12:22:48 UTC (02 16:38:29 days ago)
result success | log | rebuild
toolchain public-0.17.0
target aarch64-linux-gnu
tgt config linux_debian_12-gcc_13
timestamp 2025-08-21 12:16:06 UTC (02 16:45:10 days ago)
result success | log | rebuild
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18-O3
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18-static_O3
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18_libc++
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18_libc++-O3
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_debian_12-clang_18_libc++-static_O3
result unbuilt
toolchain public-0.17.0
target x86_64-linux-gnu
tgt config linux_fedora_39-gcc_13-bindist
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin22.5.0
tgt config macos_13-clang_15.0
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-clang_15.0
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-clang_15.0-O3
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-clang_15.0-static_O3
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-gcc_14_homebrew
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-gcc_14_homebrew-O3
result unbuilt
toolchain public-0.17.0
target x86_64-apple-darwin23.5.0
tgt config macos_14-gcc_14_homebrew-static_O3
result unbuilt