c++ - recursiva - Usando std:: variant con recursión, sin usar boost:: recursive_wrapper
recursividad (1)
Me gustaría reemplazar boost::variant
s con C ++ 17 std::variant
y deshacerme de boost::recursive_wrapper
, para eliminar completamente la dependencia en boost en el siguiente código. ¿Cómo puedo hacer eso?
#include <boost/variant.hpp>
#include <type_traits>
using v = boost::variant<int, boost::recursive_wrapper<struct s> >;
struct s
{
v val;
};
template<template <typename...> class R, typename T, typename ... Ts>
auto reduce(T t, Ts ... /*ts*/)
{
return R<T, Ts...>{t};
}
template<typename T, typename F>
T adapt(F f)
{
static_assert(std::is_convertible_v<F, T>, "");
return f;
}
int main()
{
int val1 = 42;
s val2;
auto val3 = adapt<v>(reduce<boost::variant>(val1, val2));
}
Hay dos funciones genéricas: la primera función reduce
elige en tiempo de ejecución qué argumento devolver (aquí simplemente devuelve el primer argumento por brevedad), la segunda función adapt
convierte un valor de tipo F a un valor de tipo T.
En este ejemplo, reduce
devuelve un objeto de tipo boost::variant<int, s>
que luego se convierte en un objeto de tipo boost::variant<int, boost::recursive_wrapper<s> >
.
boost::variant
se asignará a la pila para que parte de sí misma se defina recursivamente como sí misma. (También acumulará la asignación en otras situaciones, sin saber cuántas)
std::variant
no lo hará. std::variant
niega a asignar heap.
No hay forma de tener realmente una estructura que contenga una posible variante de sí misma sin una asignación dinámica, ya que tal estructura puede fácilmente mostrarse de tamaño infinito si se declara estáticamente. (Puede codificar el entero N teniendo N recursiones de no lo mismo: ningún búfer de tamaño fijo puede contener una cantidad infinita de información).
Como tal, la std::variant
almacena un puntero inteligente de algún tipo de marcador de posición de una instancia recursiva de sí mismo.
Esto puede funcionar:
struct s;
using v = std::variant< int, std::unique_ptr<s> >;
struct s
{
v val;
~s();
};
inline s::~s() = default;
y en su defecto, intente:
struct destroy_s;
struct s;
using v = std::variant<int, std::unique_ptr<s, destroy_s> >;
struct s
{
v val;
~s();
};
struct destroy_s {
void operator()(s* ptr){ delete ptr; }
};
inline s::~s() = default;
Significa que el código del cliente debe interactuar a sabiendas con unique_ptr<s>
y no directamente con la struct s
.
Si desea admitir la semántica de copia, deberá escribir un value_ptr
que haga copias y darle el equivalente de struct copy_s;
para implementar esa copia.
template<class T>
struct default_copier {
// a copier must handle a null T const* in and return null:
T* operator()(T const* tin)const {
if (!tin) return nullptr;
return new T(*tin);
}
void operator()(void* dest, T const* tin)const {
if (!tin) return;
return new(dest) T(*tin);
}
};
template<class T, class Copier=default_copier<T>, class Deleter=std::default_delete<T>,
class Base=std::unique_ptr<T, Deleter>
>
struct value_ptr:Base, private Copier {
using copier_type=Copier;
// also typedefs from unique_ptr
using Base::Base;
value_ptr( T const& t ):
Base( std::make_unique<T>(t) ),
Copier()
{}
value_ptr( T && t ):
Base( std::make_unique<T>(std::move(t)) ),
Copier()
{}
// almost-never-empty:
value_ptr():
Base( std::make_unique<T>() ),
Copier()
{}
value_ptr( Base b, Copier c={} ):
Base(std::move(b)),
Copier(std::move(c))
{}
Copier const& get_copier() const {
return *this;
}
value_ptr clone() const {
return {
Base(
get_copier()(this->get()),
this->get_deleter()
),
get_copier()
};
}
value_ptr(value_ptr&&)=default;
value_ptr& operator=(value_ptr&&)=default;
value_ptr(value_ptr const& o):value_ptr(o.clone()) {}
value_ptr& operator=(value_ptr const&o) {
if (o && *this) {
// if we are both non-null, assign contents:
**this = *o;
} else {
// otherwise, assign a clone (which could itself be null):
*this = o.clone();
}
return *this;
}
value_ptr& operator=( T const& t ) {
if (*this) {
**this = t;
} else {
*this = value_ptr(t);
}
return *this;
}
value_ptr& operator=( T && t ) {
if (*this) {
**this = std::move(t);
} else {
*this = value_ptr(std::move(t));
}
return *this;
}
T& get() { return **this; }
T const& get() const { return **this; }
T* get_pointer() {
if (!*this) return nullptr;
return std::addressof(get());
}
T const* get_pointer() const {
if (!*this) return nullptr;
return std::addressof(get());
}
// operator-> from unique_ptr
};
template<class T, class...Args>
value_ptr<T> make_value_ptr( Args&&... args ) {
return {std::make_unique<T>(std::forward<Args>(args)...)};
}
Ejemplo vivo de value_ptr.