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 11:47:25 +0100
On Fri, Sep 9, 2022 at 9:58 AM Frederick Virchanza Gotham
<cauldwell.thomas_at_[hidden]> wrote:

> But maybe we could simplify the syntax even further by using a new
> helper class "std::specify_base" as follows:
>
> std::variant< std::specify_base<Mammal>, Dog, Cat > my_mammal;
>
> my_mammal.common_base()->Speak();
>
> The helper class "std::specify_base" would be similar to how we have
> the likes of "std::in_place_t".


I've written code to achieve the syntax that uses "specify_base" for
the first type. You can download the following file without line
numbers from:

    http://www.virjacode.com/proposals_c++/variant_common_base001.cpp

Note in the following code that I would prefer if Line #129 failed to
compile, but it compiles successfully.

001: #include <variant> // variant, visit, bad_variant_access
002: #include <type_traits> // is_base_of_v, remove_cvref_t,
false_type, true_type
003: #include <tuple> // tuple, tuple_element_t
004:
005: namespace detail {
006: template <class T, template <class...> class Template>
007: struct is_specialization : std::false_type {};
008:
009: template <template <class...> class Template, class... Args>
010: struct is_specialization< Template<Args...>, Template > :
std::true_type {};
011:
012: template<class T, bool has_subtype>
013: struct subtype_or_void {
014: typedef void type;
015: };
016:
017: template<class T>
018: struct subtype_or_void<T,true> {
019: typedef std::remove_cvref_t<typename T::type> type;
020: };
021:
022: template<class T, bool has_subtype>
023: using subtype_or_void_t = typename
subtype_or_void<T,has_subtype>::type;
024:
025: } // close namespace 'detail'
026:
027: template<class Base>
028: struct specify_base {
029: typedef std::remove_cvref_t<Base> type;
030: };
031:
032: template<class... Types>
033: class my_variant : public std::variant<Types...> {
034: public:
035:
036: using std::variant<Types...>::variant; // Inherit constructors
037:
038: private:
039:
040: using FirstType = std::tuple_element_t< 0u, std::tuple<Types...> >;
041:
042: public:
043:
044: static bool constexpr is_common_base_specified =
045: detail::is_specialization<FirstType, specify_base>{};
046:
047: typedef detail::subtype_or_void_t<FirstType,
is_common_base_specified> Base;
048:
049: private:
050:
051: static Base const volatile
*detail_common_base(std::variant<Types...> const volatile *const arg)
052: {
053: static_assert(detail::is_specialization<FirstType,
specify_base>{},
054: "To invoke 'common_base()', the first type must "
055: "be a specialisation of 'specify_base'");
056:
057: std::variant<Types...> *const p = const_cast<
std::variant<Types...> * >(arg);
058:
059: auto my_lambda = []<class U>(U &u) -> Base*
060: {
061: if constexpr ( detail::is_specialization<U, specify_base>{} )
062: {
063: // Rather than return a nullptr when the
064: // currently hosted object is an 'std::specify_base<T>',
065: // throw an exception, so that we can always
066: // safely do:
067: // obj.common_base()->Invoke_Inherited_Method();
068:
069: throw std::bad_variant_access();
070: }
071: else
072: {
073: static_assert( std::is_base_of_v<Base,U>,
074: "Base class specified to
std::variant< std::specify_base<T>, MoreTypes... > "
075: "is not a base class of the
currently hosted object");
076:
077: return &u;
078: }
079: };
080:
081: return std::visit<Base*>(my_lambda, *p);
082: }
083:
084: public:
085:
086: Base *common_base(void)
087: {
088: return const_cast<Base*>( detail_common_base(this) );
089: }
090:
091: Base const *common_base(void) const
092: {
093: return const_cast<Base const *>( detail_common_base(this) );
094: }
095:
096: Base volatile *common_base(void) volatile
097: {
098: return const_cast<Base volatile *>( detail_common_base(this) );
099: }
100:
101: Base const volatile *common_base(void) const volatile
102: {
103: return const_cast<Base const volatile *>(
detail_common_base(this) );
104: }
105: };
106:
107: #include <iostream> // cout
108:
109: struct Mammal { virtual void Speak(void) const volatile = 0; };
110: struct Dog : Mammal { void Speak(void) const volatile override {
std::cout << "Dog\n"; } };
111: struct Cat : Mammal { void Speak(void) const volatile override {
std::cout << "Cat\n"; } };
112: struct Fish {}; // not a mammal!
113:
114: my_variant< specify_base<Mammal>, Dog, Cat > my_mammal;
115:
116: int main(void)
117: {
118: my_mammal.emplace<2u>();
119:
120: my_mammal.common_base()->Speak();
121:
122: // On the next line we can make a const volatile object too
123: my_variant< specify_base<Mammal>, Dog, Cat > const volatile
my_cvmammal;
124: Mammal const volatile *p = my_cvmammal.common_base(); //
This will throw 'bad_variant_access'
125:
126: // I would like if the following line didn't compile, but it does.
127: // Somehow I need to iterate through all the types and make sure
128: // that they are all derived from Mammal.
129: my_variant< specify_base<Mammal>, Dog, Cat, Fish > volatile
some_other_object;
130: // some_other_object.common_base(); - THIS WON'T COMPILE
131:
132: my_variant<int,double,long> abc;
133: // abc.common_base(); - THIS WON'T COMPILE
134: }

Received on 2022-09-09 10:47:37