10
\$\begingroup\$

This implementation of discriminated union based on C++ inherent unions and inspired by this and this articles.

#pragma once

#include <type_traits>
#include <limits>
#include <utility>

#include <cassert>

namespace versatile
{

namespace type_traits
{

template< typename from, typename to >
struct copy_ref
{

    using type = to;

};

template< typename from, typename to >
struct copy_ref< from &, to >
{

    using type = to &;

};

template< typename from, typename to >
struct copy_ref< from &&, to >
{

    using type = to &&;

};

template< typename from, typename to >
struct copy_cv
{

    using type = to;

};

template< typename from, typename to >
struct copy_cv< volatile from const, to >
{

    using type = volatile to const;

};

template< typename from, typename to >
struct copy_cv< from const, to >
{

    using type = to const;

};

template< typename from, typename to >
struct copy_cv< volatile from, to >
{

    using type = volatile to;

};

}

template< typename from, typename to >
using copy_ref = typename type_traits::copy_ref< from, to >::type;

template< typename from, typename to >
using copy_cv = typename type_traits::copy_cv< from, to >::type;

template< typename from, typename to >
using copy_refcv = copy_ref< from, copy_cv< std::remove_reference_t< from >, to > >;

template< typename type, typename ...arguments >
using result_of = decltype(std::declval< type >()(std::declval< arguments >()...));

static constexpr std::size_t npos = std::numeric_limits< std::size_t >::max();

template< std::size_t which = npos, typename type = void >
struct indexed
{

    std::size_t which_ = which;
    type value_;

    template< typename ...arguments >
    explicit
    constexpr
    indexed(arguments &&... _arguments)
        : value_(std::forward< arguments >(_arguments)...)
    { ; }

};

template<>
struct indexed< npos, void >
{

    std::size_t which_ = npos;

};

template< typename ...types >
union versatile;

template<>
union versatile<>
{

    static constexpr std::size_t which_ = npos;

private :

    using head = indexed<>;

    head head_{};

public :

    template< typename type = void >
    static
    constexpr
    std::size_t
    index() noexcept
    {
        return npos;
    }

    template< std::size_t _which >
    using at = void;

    using this_type = void;

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

    template< typename type = void >
    constexpr
    bool
    is_active() const noexcept
    {
        return false;
    }

    constexpr
    versatile() noexcept = default;

    constexpr
    void
    swap(versatile &) noexcept
    {
        return;
    }

};

template< typename first, typename ...rest >
union versatile< first, rest... >
{

    static_assert(std::is_standard_layout< first >(), "!");

    static constexpr std::size_t which_ = sizeof...(rest);

private :

    using head = indexed< which_, first >;
    using tail = versatile< rest... >;

    head head_;
    tail tail_;

public :

    template< typename type = first >
    static
    constexpr
    std::size_t
    index() noexcept
    {
        if (std::is_same< type, first >()) {
            return which_;
        } else {
            return tail::template index< type >();
        }
    }

    template< std::size_t _which >
    using at = std::conditional_t< (_which == which_), first, typename tail::template at< _which > >;

    using this_type = first;

    constexpr
    std::size_t
    which() const noexcept
    {
        return head_.which_;
    }

    template< typename type = first >
    constexpr
    bool
    is_active() const noexcept
    {
        if (std::is_same< type, first >()) {
            return (which() == which_);
        } else {
            return tail_.template is_active< type >();
        }
    }

    ~versatile() noexcept(std::is_nothrow_destructible< first >() && std::is_nothrow_destructible< tail >())
    {
        if (is_active()) {
            head_.~head();
        } else {
            tail_.~tail();
        }
    }

private :

    template< typename ...arguments >
    explicit
    constexpr
    versatile(std::true_type, arguments &&... _arguments) noexcept(std::is_nothrow_constructible< first, arguments... >())
        : head_(std::forward< arguments >(_arguments)...)
    { ; }

    template< typename ...arguments >
    explicit
    constexpr
    versatile(std::false_type, arguments &&... _arguments) noexcept(std::is_nothrow_constructible< tail, arguments... >())
        : tail_(std::forward< arguments >(_arguments)...)
    { ; }

public :

    constexpr
    versatile() noexcept(std::is_nothrow_constructible< versatile, typename std::is_default_constructible< first >::type >())
        : versatile(typename std::is_default_constructible< first >::type{})
    { ; }

    template< typename argument >
    constexpr
    versatile(argument && _argument) noexcept(std::is_nothrow_constructible< tail, argument >())
        : versatile(std::false_type{}, std::forward< argument >(_argument))
    { ; }

    template< typename ...arguments >
    explicit
    constexpr
    versatile(arguments &&... _arguments) noexcept(std::is_nothrow_constructible< versatile, typename std::is_constructible< first, arguments... >::type, arguments... >())
        : versatile(typename std::is_constructible< first, arguments... >::type{}, std::forward< arguments >(_arguments)...)
    { ; }

    versatile(versatile const & _rhs) noexcept(std::is_nothrow_copy_constructible< first >() && std::is_nothrow_copy_constructible< tail >())
    {
        if (_rhs.which() == which_) {
            ::new (&head_) head(_rhs.head_.value_);
        } else {
            ::new (&tail_) tail(_rhs.tail_);
        }
    }

    versatile(versatile & _rhs) noexcept(std::is_nothrow_copy_constructible< first >() && std::is_nothrow_copy_constructible< tail >())
    {
        if (_rhs.which() == which_) {
            ::new (&head_) head(_rhs.head_.value_);
        } else {
            ::new (&tail_) tail(_rhs.tail_);
        }
    }

    versatile(versatile const && _rhs) noexcept(std::is_nothrow_move_constructible< first >() && std::is_nothrow_move_constructible< tail >())
    {
        if (_rhs.which() == which_) {
            ::new (&head_) head(std::move(_rhs.head_.value_));
        } else {
            ::new (&tail_) tail(std::move(_rhs.tail_));
        }
    }

    versatile(versatile && _rhs) noexcept(std::is_nothrow_move_constructible< first >() && std::is_nothrow_move_constructible< tail >())
    {
        if (_rhs.which() == which_) {
            ::new (&head_) head(std::move(_rhs.head_.value_));
        } else {
            ::new (&tail_) tail(std::move(_rhs.tail_));
        }
    }

    constexpr
    versatile(first const & _rhs) noexcept(std::is_nothrow_copy_constructible< first >())
        : head_(_rhs)
    { ; }

    constexpr
    versatile(first & _rhs) noexcept(std::is_nothrow_copy_constructible< first >())
        : head_(_rhs)
    { ; }

    constexpr
    versatile(first const && _rhs) noexcept(std::is_nothrow_move_constructible< first >())
        : head_(std::move(_rhs))
    { ; }

    constexpr
    versatile(first && _rhs) noexcept(std::is_nothrow_move_constructible< first >())
        : head_(std::move(_rhs))
    { ; }

    constexpr
    versatile &
    operator = (versatile const & _rhs) noexcept(std::is_nothrow_copy_assignable< first >() && std::is_nothrow_copy_assignable< tail >())
    {
        assert(_rhs.which() == which());
        if (_rhs.which() != which_) {
            tail_ = _rhs.tail_;
            return *this;
        }
        return operator = (_rhs.head_.value_);
    }

    constexpr
    versatile &
    operator = (versatile & _rhs) noexcept(std::is_nothrow_copy_assignable< first >() && std::is_nothrow_copy_assignable< tail >())
    {
        assert(_rhs.which() == which());
        if (_rhs.which() != which_) {
            tail_ = _rhs.tail_;
            return *this;
        }
        return operator = (_rhs.head_.value_);
    }

    constexpr
    versatile &
    operator = (versatile const && _rhs) noexcept(std::is_nothrow_move_assignable< first >() && std::is_nothrow_move_assignable< tail >())
    {
        assert(_rhs.which() == which());
        if (_rhs.which() != which_) {
            tail_ = std::move(_rhs.tail_);
            return *this;
        }
        return operator = (std::move(_rhs.head_.value_));
    }

    constexpr
    versatile &
    operator = (versatile && _rhs) noexcept(std::is_nothrow_move_assignable< first >() && std::is_nothrow_move_assignable< tail >())
    {
        assert(_rhs.which() == which());
        if (_rhs.which() != which_) {
            tail_ = std::move(_rhs.tail_);
            return *this;
        }
        return operator = (std::move(_rhs.head_.value_));
    }

    constexpr
    versatile &
    operator = (first const & _rhs) noexcept(std::is_nothrow_copy_assignable< first >())
    {
        operator first &  () = _rhs;
        return *this;
    }

    constexpr
    versatile &
    operator = (first & _rhs) noexcept(std::is_nothrow_copy_assignable< first >())
    {
        operator first &  () = _rhs;
        return *this;
    }

    constexpr
    versatile &
    operator = (first const && _rhs) noexcept(std::is_nothrow_move_assignable< first >())
    {
        operator first &  () = std::move(_rhs);
        return *this;
    }

    constexpr
    versatile &
    operator = (first && _rhs) noexcept(std::is_nothrow_move_assignable< first >())
    {
        operator first &  () = std::move(_rhs);
        return *this;
    }

    template< typename rhs >
    constexpr
    versatile &
    operator = (rhs && _rhs) noexcept(std::is_nothrow_assignable< tail &, rhs >())
    {
        tail_ = std::forward< rhs >(_rhs);
        return *this;
    }

    constexpr
    void
    swap(versatile & _other) noexcept
    {
        assert(_other.which() == which());
        if (is_active()) {
            using std::swap;
            swap(_other.head_.value_, head_.value_);
        } else {
           tail_.swap( _other.tail_);
        }
    }

    explicit
    constexpr
    operator first const & () const & noexcept
    {
        assert(is_active());
        return head_.value_;
    }

    template< typename type >
    explicit
    constexpr
    operator type const & () const & noexcept
    {
        return static_cast< type const & >(tail_);
    }

    explicit
    constexpr
    operator first & () & noexcept
    {
        assert(is_active());
        return head_.value_;
    }

    template< typename type >
    explicit
    constexpr
    operator type & () & noexcept
    {
        return static_cast< type & >(tail_);
    }

    explicit
    constexpr
    operator first const && () const && noexcept
    {
        assert(is_active());
        return std::move(head_.value_);
    }

    template< typename type >
    explicit
    constexpr
    operator type const && () const && noexcept
    {
        return static_cast< type const && >(tail_);
    }

    explicit
    constexpr
    operator first && () && noexcept
    {
        assert(is_active());
        return std::move(head_.value_);
    }

    template< typename type >
    explicit
    constexpr
    operator type && () && noexcept
    {
        return static_cast< type && >(tail_);
    }

private :

    template< typename type, typename result_type, typename visitor, typename visitable, typename ...arguments >
    static
    constexpr
    result_type
    caller(visitor && _visitor, visitable && _visitable, arguments &&... _arguments)
    {
        // There is known clang++ bug #19917 for static_cast to rvalue reference. To avoid hard error here: replace "static_cast< type >(std::forward< visitable >(_visitable))" with "reinterpret_cast< type >(_visitable.head_.value_)".
        return std::forward< visitor >(_visitor)(static_cast< type >(std::forward< visitable >(_visitable)), std::forward< arguments >(_arguments)...);
    }

public :

    template< typename visitor, typename ...arguments >
    decltype(auto)
    apply_visitor(visitor && _visitor, arguments &&... _arguments) const &
    {
        using result_type = result_of< visitor, first, arguments... >;
        using caller_type = result_type (*)(visitor && _visitor, versatile const & _visitable, arguments &&... _arguments);
        static constexpr caller_type dispatcher_[1 + sizeof...(rest)] = {versatile::caller< first const &, result_type, visitor, versatile const &, arguments... >, versatile::caller< rest const &, result_type, visitor, versatile const &, arguments... >...};
        return dispatcher_[which_ - which()](std::forward< visitor >(_visitor), *this, std::forward< arguments >(_arguments)...);
    }

    template< typename visitor, typename ...arguments >
    decltype(auto)
    apply_visitor(visitor && _visitor, arguments &&... _arguments) &
    {
        using result_type = result_of< visitor, first, arguments... >;
        using caller_type = result_type (*)(visitor && _visitor, versatile & _visitable, arguments &&... _arguments);
        static constexpr caller_type dispatcher_[1 + sizeof...(rest)] = {versatile::caller< first &, result_type, visitor, versatile &, arguments... >, versatile::caller< rest &, result_type, visitor, versatile &, arguments... >...};
        return dispatcher_[which_ - which()](std::forward< visitor >(_visitor), *this, std::forward< arguments >(_arguments)...);
    }

    template< typename visitor, typename ...arguments >
    decltype(auto)
    apply_visitor(visitor && _visitor, arguments &&... _arguments) const &&
    {
        using result_type = result_of< visitor, first, arguments... >;
        using caller_type = result_type (*)(visitor && _visitor, versatile const && _visitable, arguments &&... _arguments);
        static constexpr caller_type dispatcher_[1 + sizeof...(rest)] = {versatile::caller< first const &&, result_type, visitor, versatile const &&, arguments... >, versatile::caller< rest const &&, result_type, visitor, versatile const &&, arguments... >...};
        return dispatcher_[which_ - which()](std::forward< visitor >(_visitor), std::move(*this), std::forward< arguments >(_arguments)...);
    }

    template< typename visitor, typename ...arguments >
    decltype(auto)
    apply_visitor(visitor && _visitor, arguments &&... _arguments) &&
    {
        using result_type = result_of< visitor, first, arguments... >;
        using caller_type = result_type (*)(visitor && _visitor, versatile && _visitable, arguments &&... _arguments);
        static constexpr caller_type dispatcher_[1 + sizeof...(rest)] = {versatile::caller< first &&, result_type, visitor, versatile &&, arguments... >, versatile::caller< rest &&, result_type, visitor, versatile &&, arguments... >...};
        return dispatcher_[which_ - which()](std::forward< visitor >(_visitor), std::move(*this), std::forward< arguments >(_arguments)...);
    }

};

template< typename ...types >
constexpr
void
swap(versatile< types... > & _lhs, versatile< types... > & _rhs) noexcept
{
    assert(_lhs.which() == _rhs.which());
    _lhs.swap(_rhs);
}

template< typename type >
struct is_versatile
        : std::false_type
{

};

template< typename ...types >
struct is_versatile< versatile< types... > >
        : std::true_type
{

};

template< typename versatile, typename type >
constexpr std::size_t index = versatile::template index< type >();

template< typename versatile, std::size_t index >
using at = typename versatile::template at< index >;

namespace visitation
{

template< typename type, bool = is_versatile< std::decay_t< type > >() >
struct underlying_type;

template< typename visitable >
struct underlying_type< visitable, true >
{

    using type = copy_refcv< visitable, typename std::decay_t< visitable >::this_type >;

};

template< typename general_type >
struct underlying_type< general_type, false >
{

    using type = general_type &&;

};

template< typename result_type, typename supervisitor, typename type, bool = is_versatile< std::decay_t< type > >() >
struct subvisitor;

template< typename result_type, typename supervisitor, typename visitable >
struct subvisitor< result_type, supervisitor, visitable, true >
{

    supervisitor && supervisitor_;
    visitable && visitable_;

    template< typename ...visited >
    constexpr
    result_type
    operator () (visited &&... _visited) const
    {
        return std::forward< visitable >(visitable_).apply_visitor(std::forward< supervisitor >(supervisitor_), std::forward< visited >(_visited)...);
    }

};

template< typename result_type, typename supervisitor, typename type >
struct subvisitor< result_type, supervisitor, type, false >
{

    supervisitor && supervisitor_;
    type && value_;

    template< typename ...visited >
    constexpr
    result_type
    operator () (visited &&... _visited) const
    {
        return std::forward< supervisitor >(supervisitor_)(std::forward< type >(value_), std::forward< visited >(_visited)...);
    }

};

template< typename result_type, typename ...visitables >
struct visitor_partially_applier;

template< typename result_type >
struct visitor_partially_applier< result_type >
{

    template< typename visitor >
    constexpr
    result_type
    operator () (visitor && _visitor) const
    {
        return std::forward< visitor >(_visitor)();
    }

};

template< typename result_type, typename first, typename ...rest >
struct visitor_partially_applier< result_type, first, rest... >
{

    template< typename visitor >
    constexpr
    result_type
    operator () (visitor && _visitor, first && _first, rest &&... _rest) const
    {
        subvisitor< result_type, visitor, first > subvisitor_{std::forward< visitor >(_visitor), std::forward< first >(_first)};
        return visitor_partially_applier< result_type, rest... >{}(subvisitor_, std::forward< rest >(_rest)...);
    }

};

}

template< typename visitor, typename first, typename ...rest >
constexpr
decltype(auto)
apply_visitor(visitor && _visitor, first && _first, rest &&... _rest)
{
    using namespace visitation;
    using result_type = result_of< visitor, typename underlying_type< first >::type, typename underlying_type< rest >::type... >;
    return visitor_partially_applier< result_type, first, rest... >{}(std::forward< visitor >(_visitor), std::forward< first >(_first), std::forward< rest >(_rest)...);
}

namespace visitation
{

template< typename visitor >
struct delayed_visitor_applier
{

    visitor visitor_;

    template< typename first, typename ...rest >
    decltype(auto)
    operator () (first && _first, rest &&... _rest) const &
    {
        return apply_visitor(visitor_, std::forward< first >(_first), std::forward< rest >(_rest)...);
    }

    template< typename first, typename ...rest >
    decltype(auto)
    operator () (first && _first, rest &&... _rest) &
    {
        return apply_visitor(visitor_, std::forward< first >(_first), std::forward< rest >(_rest)...);
    }

    template< typename first, typename ...rest >
    decltype(auto)
    operator () (first && _first, rest &&... _rest) const &&
    {
        return apply_visitor(std::move(visitor_), std::forward< first >(_first), std::forward< rest >(_rest)...);
    }

    template< typename first, typename ...rest >
    decltype(auto)
    operator () (first && _first, rest &&... _rest) &&
    {
        return apply_visitor(std::move(visitor_), std::forward< first >(_first), std::forward< rest >(_rest)...);
    }

};

}

template< typename visitor >
constexpr
visitation::delayed_visitor_applier< visitor >
apply_visitor(visitor && _visitor) noexcept
{
    return {std::forward< visitor >(_visitor)};
}

}

little test .cpp-file contents:

#include "versatile.hpp"

#include <string>
#include <array>
#include <utility>
#ifdef _DEBUG
#include <iostream>
#include <iomanip>
#endif

#include <cstdlib>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wmissing-braces"
namespace
{

struct introspector
{

    template< typename ...types >
    std::string
    operator () (types...) const
    {
        return __PRETTY_FUNCTION__;
    }

};

template< std::size_t I >
struct T
{

};

struct visitor
{

    template< std::size_t ...I >
    std::array< std::size_t, sizeof...(I) >
    operator () (T< I > const &...) const
    {
        return {I...};
    }

};

template< typename L, typename ...R >
bool
lvaluelizer(L const & _lhs, R const &... _rhs) // To avoid known clang bug #19917 https://llvm.org/bugs/show_bug.cgi?id=19917 makes all parametres lvalues.
{
    using versatile::apply_visitor;
    return (_lhs == apply_visitor(visitor{}, _rhs...));
}

template< std::size_t ...M, std::size_t ...N >
bool
invoke(std::index_sequence< M... > const &, std::index_sequence< N... > const &)
{
    using versatile::versatile;
    return lvaluelizer(std::array< std::size_t, sizeof...(N) >{(N % sizeof...(M))...}, versatile< T< M >... >{T< (N % sizeof...(M)) >{}}...);
}

template< std::size_t M, std::size_t N = M >
bool
test()
{
    return invoke(std::make_index_sequence< M >{}, std::make_index_sequence< N >{});
}

}
#pragma clang diagnostic pop

int
main()
{
    using namespace versatile;
    {
        using V0 = versatile<>;
        assert(!V0{}.is_active());

        using V1 = versatile< V0 >;
        assert(V1{}.which() == 0);
        assert((index< V1, V0 > == 0));

        using V2 = versatile< V1, V0 >;
        assert(V2{}.which() == 1);
        assert((index< V2, V1 > == 1));

        using V3 = versatile< V2, V1, V0 >;
        assert(V3{}.which() == 2);
        assert((index< V3, V2 > == 2));

        using V4 = versatile< V3, V2, V1, V0 >;
        assert(V4{}.which() == 3);
        assert((index< V4, V3 > == 3));

        using V5 = versatile< V4, V3, V2, V1, V0 >;
        assert(V5{}.which() == 4);
        assert((index< V5, V4 > == 4));
        static_assert(std::is_same< V4, at< V5, 4 > >(), "!");
    }
    {
        struct empty {};
        using V = versatile< empty, int, float, double, long double >;

        assert(V{}.is_active());
        assert(V{}.which() == 4);
        assert(V{0}.which() == 3);
        assert(V{1.0f}.which() == 2);
        assert(V{2.0}.which() == 1);
        assert(V{3.0L}.which() == 0);
    }
    {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-member-function"
        struct A { explicit A(int, int) {} };
#pragma clang diagnostic pop
        struct B { explicit B(int, double) {} };
        using V = versatile< B, A, int >;
        V v0{1};
        assert((v0.which() == index< V, int >));
        V v1{1, 2};
        assert((v1.which() == index< V, B >)); // !
        V v2{1, 2.0};
        assert((v2.which() == index< V, B >));
    }
    {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunneeded-member-function"
        struct A { ~A() noexcept(true) {} };
        struct B { ~B() noexcept(false) {} };
#pragma clang diagnostic pop
        using VA = versatile< A >;
        using VB = versatile< B >;
        using VAB = versatile< A, B >;
        static_assert(std::is_nothrow_destructible< A >(), "!");
        static_assert(!std::is_nothrow_destructible< B >(), "!");
        static_assert(std::is_nothrow_destructible< VA >(), "!");
        static_assert(!std::is_nothrow_destructible< VB >(), "!");
        static_assert(!std::is_nothrow_destructible< VAB >(), "!");
    }
    {
        struct A { A() {} A & operator = (A const &) = default; A & operator = (A &) = delete; };
        using V = versatile< A >;
        V v;
        A const a{};
        v = a;
    }
    {
        struct A {};
        struct B {};
        struct L
        {

            int operator () (A) { return 100; }
            int operator () (B) { return 200; }

        };
        L v;
        using V = versatile< A, B >;
        static_assert(std::is_same< typename visitation::underlying_type< V >::type, A >());
        V a{A{}};
        assert(a.apply_visitor(v) == 100);
        V b{B{}};
        assert(b.apply_visitor(v) == 200);
    }
    {
        using V = versatile< int >;
        V a = +1;
        V b = -1;
        assert(static_cast< int & >(a) == +1);
        assert(static_cast< int & >(b) == -1);
        a.swap(b);
        assert(static_cast< int & >(a) == -1);
        assert(static_cast< int & >(b) == +1);
        swap(a, b);
        assert(static_cast< int & >(a) == +1);
        assert(static_cast< int & >(b) == -1);
    }
    {
        struct A {};
        struct B {};
        struct C {};
        struct D {};
        using AD = versatile< A, D >;
        using BA = versatile< B, A >;
        using CB = versatile< C, B >;
        using DC = versatile< D, C >;
        AD ad;
        BA ba;
        CB cb;
        DC dc;
        introspector introspector_;
        assert(apply_visitor(introspector_, ad, ba, cb, dc) == introspector_(A{}, B{}, C{}, D{}));
        AD da{D{}};
        BA ab{A{}};
        CB bc{B{}};
        DC cd{C{}};
        assert(apply_visitor(introspector_, da, ab, bc, cd) == introspector_(D{}, A{}, B{}, C{}));
    }
    {
        test< 5 >();
    }
    return EXIT_SUCCESS;
}

What do you think about the code (style, correctness and other aspects)? What about conformity with (modern, i.e. C++1z) orthodox canonical class form?

What about explicit conversion operators using?

Here the repository containing the last version.

\$\endgroup\$
  • 2
    \$\begingroup\$ Personally, I do not recommend using a conditionally noexcept destructor. I would rather go with the STL approach and say: if the destructor of any subtype throws, you have an UB. What business would anyone have in putting a T inside your wrapper that throws in destructor? \$\endgroup\$ – Andrzej Mar 30 '15 at 13:03
1
\$\begingroup\$

I've got a bunch of style nits (and more than nits). Sadly, nothing about the actual behavior or correctness of the code — there's just too much code there to get a sense of what it does and what it's supposed to do.


template< typename from, typename to >
struct copy_cv< volatile from const, to >
{

    using type = volatile to const;

};

(A) Way too many blank lines here. The more gratuitous newlines you use, the less code fits on my screen.

(B) It's certainly unusual to place volatile before the type and const after the type. Pick one or the other and stick with it.

(C) Others may disagree, but I find the lowercase names from and to distasteful; they look too much like keywords and not enough like template type parameters. I've gratuitously CamelCased them in my code below.

Improved:

template<typename From, typename To>
struct copy_cv<const volatile From, To> {
    using type = const volatile To;
};

In fact, you could take a page from Walter Brown's playbook and introduce type_is<T>:

template<typename T> struct type_is { using type = T; };

template<typename From, typename To> struct copy_cv : type_is<To> {};
template<typename From, typename To> struct copy_cv<const From, To> : type_is<const To> {};
template<typename From, typename To> struct copy_cv<volatile From, To> : type_is<volatile To> {};
template<typename From, typename To> struct copy_cv<const volatile From, To> : type_is<const volatile To> {};

There — in six lines I've implemented all four specializations, where your code took seven lines just to implement one!


recursive_wrapper &
operator = (recursive_wrapper && _rhs) &
{
    operator type & () = static_cast< type && >(_rhs);
    return *this;
}

It's confusing that you use operator type & on the left but a static_cast on the right. Pick one syntax and stick with it, unless there's a good reason you can't. I suggest:

recursive_wrapper& operator=(recursive_wrapper&& _rhs) & {
    static_cast<type&>(*this) = static_cast<type&&>(_rhs);
    return *this;
}

If this doesn't work, then my code-review comment is that you need more comments in your code. :)

Also note in passing that naming function parameters with leading underscores (_rhs) is yet another unusual convention of yours. Most people avoid leading underscores, although this one is technically okay. I've seen "trailing underscore on member variable" as a convention, but "leading underscore on function parameter" is a new one.


In recursive_wrapper you've got a bunch of largely identical member functions, overloaded on type and const type and volatile type and so on. Could you replace large swaths of these overloads with a template instead? You could use SFINAE to disable the template for types T where is_same_v<remove_cv_t<T>, type> wasn't true.


using versatile = versatile< types... >;

No. Just, no.

Each name in your program should mean one thing and one thing only. Making the same name mean both "a template class" and "a particular specialization of that template class" is just asking for confusion.


versatile(versatile const & _rhs) noexcept(std::is_nothrow_copy_constructible< first >() && std::is_nothrow_copy_constructible< tail >())
{
    if (_rhs.which() == which_) {
        ::new (&head_) head(_rhs.head_.value_);
    } else {
        ::new (&tail_) tail(_rhs.tail_);
    }
}

versatile(versatile & _rhs) noexcept(std::is_nothrow_copy_constructible< first >() && std::is_nothrow_copy_constructible< tail >())
{
    if (_rhs.which() == which_) {
        ::new (&head_) head(_rhs.head_.value_);
    } else {
        ::new (&tail_) tail(_rhs.tail_);
    }
}

Looks to me as if the second half of this code snippet is completely redundant. Either that, or again you're missing some code comments explaining why "copy-assignment from a mutable RHS" ought to behave differently from "copy-assignment from a const RHS".


template< std::size_t I >
struct T
{

};

The name T is highly likely to be mistaken for a template type parameter. I strongly recommend the Boost-MPL-style

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

and then use e.g. int_<42> where you were previously using T<42>.

\$\endgroup\$
  • \$\begingroup\$ What reason to use static_cast< type & >(*this)? Here static_cast, pointer, and dereferencing? I just reuse already defined member function, when writing operator type & () on left hand side, nothing else here. It's clear. \$\endgroup\$ – Orient Apr 7 '15 at 4:28
  • \$\begingroup\$ Most of your suspicions regarding of a bunch of largely identical member functions have no substantial underground. Here some hints akrzemi1.wordpress.com/2015/03/02/… . It is not simple matter to make compiler to distinguish all kinds of constructors or assignment operators overloadings as you intent. \$\endgroup\$ – Orient Apr 7 '15 at 4:33
  • \$\begingroup\$ using versatile = versatile< types... >; why? Very clear moment from any point of view. \$\endgroup\$ – Orient Apr 7 '15 at 4:34
  • \$\begingroup\$ Without explicitely defined versatile(versatile &) such a versatile & parameter will choose templated overloading case nor versatile(versatile const &). \$\endgroup\$ – Orient Apr 7 '15 at 7:42
  • \$\begingroup\$ "Each name in your program should mean one thing and one thing only.", I have to correct you: "Each name in each particular namespace...". A class have its own scope. \$\endgroup\$ – Orient Apr 7 '15 at 7:46

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.