3
\$\begingroup\$

I want to show you how to implement variant like boost::variant, but almost for constexpr, not runtime:

#include <type_traits>
#include <utility>

template< std::size_t i >
using I = std::integral_constant< std::size_t, i >;

template< std::size_t i >
struct index : I< i > {};

template< bool ...b >
struct get_i;
template<>
struct get_i<> {};
template< bool ...rest >
struct get_i< true, rest... > : index< sizeof...(rest) > {};
template< bool ...rest >
struct get_i< false, rest... > : get_i< rest... > {};
template< typename ...types >
using get_di = get_i< std::is_default_constructible< types >::value... >;

template< typename type, typename ...types >
struct i_at {};
template< typename type, typename ...rest >
struct i_at< type, type, rest... > : index< sizeof...(rest) > {};
template< typename type, typename first, typename ...rest >
struct i_at< type, first, rest... > : i_at< type, rest... > {};
template< typename type, typename ...types >
using i_at_t = typename i_at< type, types... >::type;

template< bool td, typename ...types >
struct C;

template< bool td >
struct C< td >
{
    void destruct(std::size_t) { ; }
};

template< typename first, typename ...rest >
struct C< true, first, rest... >
{
    using head = first;
    using tail = C< true, rest... >;

    union
    {
        head head_;
        tail tail_;
    };

    constexpr C() = default;

    template< typename ...A >
    constexpr C(I< (1 + sizeof...(rest)) >, A &&... a)
        : head_(std::forward< A >(a)...)
    { ; }

    template< typename ...A >
    constexpr C(A &&... a)
        : tail_(std::forward< A >(a)...)
    { ; }

    constexpr operator first const & () const { return head_; }
    constexpr operator first & () { return head_; }
    template< typename T >
    constexpr operator T const & () const { return tail_; }
    template< typename T >
    constexpr operator T & () { return tail_; }
};

template< typename first, typename ...rest >
struct C< false, first, rest... >
{
    using head = first;
    using tail = C< false, rest... >;

    union
    {
        head head_;
        tail tail_;
    };

    constexpr C() = default;

    C(C const &) = default;
    C(C &) = default;
    C(C &&) = default;
    C & operator = (C const &) = default;
    C & operator = (C &) = default;
    C & operator = (C &&) = default;

    ~C() { /*tail_.~tail();*/ }

    void destruct(std::size_t w) { (w == sizeof...(rest) + 1) ? head_.~head() : tail_.destruct(w); }

    template< typename ...A >
    constexpr C(I< (1 + sizeof...(rest)) >, A &&... a)
        : head_(std::forward< A >(a)...)
    { ; }

    template< typename ...A >
    constexpr C(A &&... a)
        : tail_(std::forward< A >(a)...)
    { ; }

    constexpr operator first const & () const { return head_; }
    constexpr operator first & () { return head_; }
    template< typename T >
    constexpr operator T const & () const { return tail_; }
    template< typename T >
    constexpr operator T & () { return tail_; }
};

template< bool td, bool tdc, typename ...types >
struct D;

template< typename ...types >
struct D< true, true, types... >
{
    using storage = C< true, types... >;

    std::size_t w;
    storage c;

    constexpr std::size_t which() const { return (w == 0) ? sizeof...(types) : w; }

    constexpr D() = default;

    template< typename i, typename ...A >
    constexpr D(i, A &&... a)
        : w(i::value)
        , c(i{}, std::forward< A >(a)...)
    { ; }

    template< typename T >
    constexpr operator T const & () const { return c; }
    template< typename T >
    constexpr operator T & () { return c; }
};

template< typename ...types >
struct D< false, true, types... >
{
    using storage = C< false, types... >;

    std::size_t w;
    storage c;

    constexpr std::size_t which() const { return (w == 0) ? sizeof...(types) : w; }

    constexpr D() = default;

    D(D const &) = default;
    D(D &) = default;
    D(D &&) = default;
    D & operator = (D const &) = default;
    D & operator = (D &) = default;
    D & operator = (D &&) = default;

    ~D() { if (w != 0) c.destruct(w); }

    template< typename i, typename ...A >
    constexpr D(i, A &&... a)
        : w(i::value)
        , c(i{}, std::forward< A >(a)...)
    { ; }

    template< typename T >
    constexpr operator T const & () const { return c; }
    template< typename T >
    constexpr operator T & () { return c; }
};

template< typename ...types >
struct D< true, false, types... >
{
    using storage = C< true, types... >;

    std::size_t w;
    storage c;

    constexpr std::size_t which() const { return w; }

    using di = get_di< types..., void >;

    constexpr D() : D(typename di::type{}) { ; }

    template< typename i, typename ...A >
    constexpr D(i, A &&... a)
        : w(i::value)
        , c(i{}, std::forward< A >(a)...)
    { ; }

    template< typename T >
    constexpr operator T const & () const { return c; }
    template< typename T >
    constexpr operator T & () { return c; }
};

template< typename ...types >
struct D< false, false, types... >
{
    using storage = C< false, types... >;

    std::size_t w;
    storage c;

    constexpr std::size_t which() const { return w; }

    using di = get_di< types..., void >;

    constexpr D() : D(typename di::type{}) { ; }

    D(D const &) = default;
    D(D &) = default;
    D(D &&) = default;
    D & operator = (D const &) = default;
    D & operator = (D &) = default;
    D & operator = (D &&) = default;

    ~D() { c.destruct(w); }

    template< typename i, typename ...A >
    constexpr D(i, A &&... a)
        : w(i::value)
        , c(i{}, std::forward< A >(a)...)
    { ; }

    template< typename T >
    constexpr operator T const & () const { return c; }
    template< typename T >
    constexpr operator T & () { return c; }
};

template< typename ...types >
using D_t = D< (std::is_trivially_destructible< types >::value && ...), (std::is_trivially_default_constructible< types >::value && ...), types... >;

template< bool dc >
struct DC;

template<>
struct DC< true >
{
    constexpr DC() = default;
    constexpr DC(void *) { ; }
};

template<>
struct DC< false >
{
    constexpr DC() = delete;
    constexpr DC(void *) { ; }
};

template< typename ...types >
using DC_t = DC< (std::is_default_constructible< types >::value || ...) >;

template< typename ...types >
class V
    : DC_t< types... >
{
    using dc = DC_t< types... >;
    using storage = D_t< types... >;

    storage d;

public :

    template< typename T >
    using i_at_t = i_at_t< T, types..., void >;

    constexpr
    std::size_t which() { return d.which(); }

    constexpr V() = default;

    template< typename T, typename i = i_at_t< T > >
    constexpr V(T && x) : dc({}), d(i{}, std::forward< T >(x)) { ; }

    template< typename T, typename = i_at_t< T > >
    explicit constexpr operator T const & () const { return d; }
    template< typename T, typename = i_at_t< T > >
    explicit constexpr operator T & () { return d; }

    template< typename T, typename = i_at_t< T > >
    constexpr V & operator = (T && x) { return (*this = V(std::forward< T >(x))); }

    template< std::size_t i, typename ...A >
    constexpr V(index< i >, A &&... a) : dc({}), d(I< i >{}, std::forward< A >(a)...) { ; }
};

inline constexpr bool test()
{
    {
        struct A {};
        V< int, double, A > v{};
        // ^    ^       ^
        // 3    2       1
        if (v.which() != 3) return false;
        v = 2;
        if (static_cast< int & >(v) != 2) return false;
        v = A{};
        if (v.which() != 1) return false;
        v = -1.0;
        if (v.which() != 2) return false;
        if (static_cast< double & >(v) < -1.0) return false;
        if (static_cast< double & >(v) > -1.0) return false;
    }
    { // emplacement constructor
        struct A { constexpr A(int) { ; } };
        struct B {};
        struct C {};
        using U = V< A, B, C >;
        U u{};
        if (u.which() != 2) return false;
        U v{index< 1 >{}};
        if (v.which() != 1) return false;
        U w{index< 3 >{}, 1};
        if (w.which() != 3) return false;
    }
    return true;
}

static_assert(test());

LIVE EXAMPLE

It still correct for arbitrary alternative types, but it provides full functionality only if all the alternative types are TiviallyCopyable. Now you can generate AST using compile time parsers (joke).

The functionality is:

  • default constructor (for first leftmost default constructible alternative type, if any)
  • conversion constructor
  • copy/move-constructors
  • copy/move-assignment operators.
  • conversion ("from") operator (access to stored value)
  • conversion assignment ("to") operator (can alter active member at even compile time)
  • discriminator which(), indexing is right to left one-based
  • emplace constructor

Please, don't criticize the naming. Current naming is just for brevity. Can I request a criticism of design flaws (or maybe there is another site for such requests?)?

ADDITIONAL:

There is one restriction on using V on the stack (automatic storage duration) or when it casted from sutiable sized and aligned storage using reinterpret_cast: it must be value-initialized (and, therefore, zero-initialized), nor default-initialized, the storage should be filled by zeros.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.