Date: Mon, 2 Jan 2023 16:39:11 +0000
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.
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.
Received on 2023-01-02 16:39:13