C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Halfway between 'variant' and 'any'

From: Edward Catmur <ecatmur_at_[hidden]>
Date: Mon, 2 Jan 2023 17:15:31 +0000
This sounds quite a lot like polymorphic_value https://wg21.link/P0201

You could accomplish the inplace part with an appropriate allocator.

On Mon, 2 Jan 2023, 16:39 Frederick Virchanza Gotham via Std-Proposals, <
std-proposals_at_[hidden]> wrote:

> It would be very useful to have a class that is halfway between 'variant'
> and 'any'. What I want is a class with the versatility off 'any' except
> that it allocates on the stack rather than on the heap.
>
> A few months ago I proposed an alteration to the 'variant' class which
> would allow it to be used as a pointer to a common base class which is
> shared by all the specified derived classes. I proposed back then that you
> would define an object as follows:
>
> variant_common_base<Base,Derived1,Derived2> my_object;
>
> The drawback here is that the translation unit containing the definition
> of 'my_object' must be aware of, and contain the definitions of the
> classes, Derived1 and Derived2. Also in the future if we add a third
> derived class, we must revise the definition of 'my_object' as follows:
>
> variant_common_base<Base,Derived1,Derived2,Derived3> my_object;
>
> Well with the new class I'm proposing today, which I call 'derivative',
> you simply specify the base class:
>
> derivative<Base> my_object;
>
> Then you can use it to host any object which satisfies the following two
> criteria:
> (1) The object publicly inherits from Base
> (2) The object size is no greater than 4 * sizeof(Base) -- you can
> customise this limit
>
> Here's the code I've written so far. I wrote this on my Android phone and
> so my compiler doesn't have concepts so I've used 'static_assert' instead.
> Note that I keep track of the base pointer in order to accommodate complex
> multiple virtual inheritance:
>
> #include <cassert> // assert
> #include <cstddef> // size_t, max_align_t
> #include <new> // 'placement new'
> #include <utility> // forward
> #include <functional> // function
> #include <type_traits> // instead of concepts
>
> // My compiler doesn't have 'derived_from' so
> // I've written it on the next line
> template<class Derived,class Base>
> bool constexpr derived_from =
> std::is_base_of_v<Base, Derived> &&
> std::is_convertible_v<const volatile Derived*, const volatile Base*>;
>
> template<class T,
> std::size_t buflen = 4u * sizeof(T)>
> class derivative {
> T *p_base = nullptr;
> std::function<void(void)> destroy;
> alignas(std::max_align_t) char unsigned buf[buflen];
>
> public:
> void reset(void)
> {
> if ( destroy ) destroy();
> destroy = nullptr;
> p_base = nullptr;
> }
>
> ~derivative(void) { reset(); }
>
> template<class U, class... Args>
> void emplace(Args&&... args)
> {
> static_assert(derived_from<U,T>);
> static_assert(buflen >= sizeof(U));
>
> reset();
>
> U *const p_derived = static_cast<U*>(static_cast<void*>(buf));
>
> ::new(p_derived) U( std::forward<Args>(args)... );
>
> this->p_base = p_derived;
>
> destroy = [p_derived](void)
> {
> p_derived->~U();
> };
> }
>
> T *operator->(void)
> {
> assert( nullptr != p_base );
> assert( static_cast<bool>(destroy) );
> return p_base;
> }
>
> bool has_value(void) { return static_cast<bool>(destroy); }
> };
>
> #include <sstream>
> #include <fstream>
>
> derivative<std::istream> s;
>
> int main(void)
> {
> s.emplace<std::ifstream>("c:\\autoexec.bat");
> s->clear();
> s.emplace<std::istringstream>();
> s->clear();
> }
>
> Note that std::function doesn't use dynamic allocation until the lambda
> captures exceed 16 bytes (and I'm only using 8 bytes for one pointer).
>
> A more simple class than 'derivative' could be made if we remove the
> common Base class requirement. I would call this other class 'generic' and
> I'd write it something like as follows:
>
> #include <cstddef> // size_t, max_align_t
> #include <new> // 'placement new'
> #include <utility> // forward
> #include <functional> // function
> #include <typeinfo> // typeid, type_info
> #include <exception> // exception
>
> template<std::size_t buflen>
> class generic {
> struct bad_generic_access : std::exception {};
>
> std::function<void(void)> destroy;
>
> alignas(std::max_align_t) char unsigned buf[buflen];
>
> std::type_info const *ti = nullptr;
>
> public:
> void reset(void)
> {
> if ( nullptr == ti ) return;
>
> ti = nullptr;
> if ( destroy ) destroy();
> destroy = nullptr;
> }
>
> ~generic(void) { reset(); }
>
> template<class U, class... Args>
> void emplace(Args&&... args)
> {
> static_assert(buflen >= sizeof(U));
>
> reset();
>
> U *const p = static_cast<U*>(static_cast<void*>(buf));
>
> ::new(p) U( std::forward<Args>(args)... );
>
> destroy = [p](void)
> {
> if constexpr ( std::is_class_v<U> ) p->~U();
> };
>
> ti = &typeid(*p);
> }
>
> bool has_value(void) const { return nullptr != ti; }
>
> template<class U>
> U &get(void)
> {
> if ( false == has_value() ) throw bad_generic_access();
>
> if ( typeid(U) != *ti ) throw bad_generic_access();
>
> return *static_cast<U*>(static_cast<void*>(buf));
> }
> };
>
> #include <sstream>
> #include <fstream>
>
> generic<8192u> s;
>
> int main(void)
> {
> s.emplace<std::ifstream>("c:\\autoexec.bat");
> s.get<std::ifstream>().clear();
> s.emplace<std::istringstream>();
> s.get<std::istringstream>().clear();
> s.emplace<int>(5);
>
> s.get<int>() += 2;
> }
>
> So you can use 'generic' just like 'any' except it allocates on the stack
> rather than the heap.
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>

Received on 2023-01-02 17:15:46