C++ Logo

std-proposals

Advanced search

Re: [std-proposals] New method 'common_base' for 'std::variant'

From: Frederick Virchanza Gotham <cauldwell.thomas_at_[hidden]>
Date: Fri, 9 Sep 2022 23:41:57 +0100
On Fri, Sep 9, 2022 at 11:47 AM Frederick Virchanza Gotham
<cauldwell.thomas_at_[hidden]> wrote:
>
> On Fri, Sep 9, 2022 at 9:58 AM Frederick Virchanza Gotham
>>
> > std::variant< std::specify_base<Mammal>, Dog, Cat > my_mammal;
> >
> > my_mammal.common_base()->Speak();


I realised just now that "std::variant" doesn't have a definition for
"operator->", and so I can simplify the syntax even further as
follows:

            std::variant< std::specify_base<Mammal>, Dog, Cat > my_mammal;

            my_mammal->Speak(); // throws 'bad_variant_access' if
there's no object

I have changed my code as follows:

#include <variant> // variant, visit, bad_variant_access
#include <type_traits> // is_base_of_v, remove_cvref_t, false_type, true_type
#include <tuple> // tuple, tuple_element_t

namespace detail {
    template <class T, template <class...> class Template>
    struct is_specialization : std::false_type {};

    template <template <class...> class Template, class... Args>
    struct is_specialization< Template<Args...>, Template > : std::true_type {};

    template<class T, bool has_subtype>
    struct subtype_or_void {
        typedef void type;
    };

    template<class T>
    struct subtype_or_void<T,true> {
        typedef std::remove_cvref_t<typename T::type> type;
    };

    template<class T, bool has_subtype>
    using subtype_or_void_t = typename subtype_or_void<T,has_subtype>::type;

} // close namespace 'detail'

template<class Base>
struct specify_base {
    typedef std::remove_cvref_t<Base> type;
};

template<class... Types>
class my_variant : public std::variant<Types...> {
public:

    using std::variant<Types...>::variant; // Inherit constructors

private:

    using FirstType = std::tuple_element_t< 0u, std::tuple<Types...> >;

public:

    static bool constexpr is_common_base_specified =
        detail::is_specialization<FirstType, specify_base>{};

    typedef detail::subtype_or_void_t<FirstType, is_common_base_specified> Base;

private:

    static Base const volatile
*detail_common_base(std::variant<Types...> const volatile *const arg)
    {
        static_assert(detail::is_specialization<FirstType, specify_base>{},
                      "To invoke 'common_base()', the first type must "
                      "be a specialisation of 'specify_base'");

        std::variant<Types...> *const p = const_cast<
std::variant<Types...> * >(arg);

        auto my_lambda = []<class U>(U &u) -> Base*
          {
            if constexpr ( detail::is_specialization<U, specify_base>{} )
            {
                // Rather than return a nullptr when the
                // currently hosted object is an 'std::specify_base<T>',
                // throw an exception, so that we can always
                // safely do:
                // my_variant_object->Inherited_Method();

                throw std::bad_variant_access();
            }
            else
            {
                static_assert( std::is_base_of_v<Base,U>,
                             "Base class specified to std::variant<
std::specify_base<T>, MoreTypes... > "
                             "is not a base class of the currently
hosted object");

                return &u;
            }
          };

        return std::visit<Base*>(my_lambda, *p);
    }

public:

    Base *operator->(void)
    {
        return const_cast<Base*>( detail_common_base(this) );
    }

    Base const *operator->(void) const
    {
        return const_cast<Base const *>( detail_common_base(this) );
    }

    Base volatile *operator->(void) volatile
    {
        return const_cast<Base volatile *>( detail_common_base(this) );
    }

    Base const volatile *operator->(void) const volatile
    {
        return const_cast<Base const volatile *>( detail_common_base(this) );
    }
};

#include <iostream> // cout

struct Mammal { virtual void Speak(void) const volatile = 0; };
struct Dog : Mammal { void Speak(void) const volatile override {
std::cout << "Dog\n"; } };
struct Cat : Mammal { void Speak(void) const volatile override {
std::cout << "Cat\n"; } };
struct Fish {}; // not a mammal!

my_variant< specify_base<Mammal>, Dog, Cat > my_mammal;

int main(void)
{
    my_mammal.emplace<2u>();

    my_mammal->Speak();

    // On the next line we can make a const volatile object too
    my_variant< specify_base<Mammal>, Dog, Cat > const volatile my_cvmammal;
    my_cvmammal->Speak(); // This will throw 'bad_variant_access'

    // I would like if the following line didn't compile, but it does.
    // Somehow I need to iterate through all the types and make sure
    // that they are all derived from Mammal.
    my_variant< specify_base<Mammal>, Dog, Cat, Fish > volatile
some_other_object;
    // some_other_object->Speak(); - THIS WON'T COMPILE

    my_variant<int,double,long> abc;
}

Received on 2022-09-09 22:42:09