GithubHelp home page GithubHelp logo

eppendorf-sw / strong_type Goto Github PK

View Code? Open in Web Editor NEW

This project forked from rollbear/strong_type

0.0 1.0 0.0 216 KB

An additive strong typedef library for C++14/17/20

License: Boost Software License 1.0

C++ 96.66% CMake 3.34%

strong_type's Introduction

strong_type

An additive strong typedef library for C++14/17/20 using the Boost Software License 1.0

CI Build Status codecov

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 number of small utilities are available directly in strong_type/type.hpp.

  • strong::type provides a non-member swap() function as a friend, which swaps underlying values using.

  • strong::underlying_type<Type> is T for strong::type<T, Tag, Ms...> and public descendants, and Type for other types.

  • strong::uninitialized can be used to construct instances of strong::type<T...> without initializing the value. This is only possible if the underlying type is trivially 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 of strong::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, like strong::ordered_with<Ts...>, it tests each of the types Ts individually. 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.

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);
        }
    };
};

This repetition quictly gets old. You can use a template trick to get rid of it:

template <auto no_value>
struct optional
{
    template <typename T> // This is the strong type
    struct modifier
    {
        constexpr bool has_value() const noexcept;
        template <typename TT>
        constexpr friend decltype(auto) operator*(TT&& tt) noexcept
        {
            return value_of(std::forward<TT>(tt));
        }
    };
};

Yes, operators can be written as inline friends, which then accepts the type of the object as the parameter. This is often referred to as the "hidden friend idiom". By making the type a forwarding reference, this will match T&, T&&, const T&, const T&&, volatile T&, volatile T&&,const volatile T& and const volatile T&&. 8 overloads in one definition! Not bad.

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;
        template <typename TT>
        constexpr friend decltype(auto) operator*(TT&& tt) 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 here. 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;
        template <typename TT>
        constexpr friend decltype(auto) operator*(TT&& tt) 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;
        }
        template <typename TT>
        friend constexpr decltype(auto) operator*(TT&& self) noexcept
        {
            return value_of(std::forward<TT>(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));
        }
    };
};

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.

Library Author
type_safe Jonathan Müller
NamedType Jonathan Boccara
strong_typedef Anthony Williams (justsoftwaresolutions)
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

strong_type's People

Contributors

rollbear avatar hazardyknusperkeks avatar adriweb avatar floriankramer avatar yog-muskrat avatar timblechmann avatar wh5a avatar dsiroky avatar yumeyao avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.