Подтвердить что ты не робот

Использование std:: variant с рекурсией без использования boost:: recursive_wrapper

Я хочу заменить boost::variant на С++ 17 std::variant и избавиться от boost::recursive_wrapper, чтобы полностью удалить зависимость от boost в следующем коде. Как я могу это сделать?

#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));
}

Существуют две общие функции: первая функция reduce выбирает во время выполнения, какой аргумент возвращает (здесь он просто возвращает первый аргумент для краткости), вторая функция adapt преобразует значение типа F в значение типа T.

В этом примере reduce возвращает объект типа boost::variant<int, s>, который затем преобразуется в объект типа boost::variant<int, boost::recursive_wrapper<s> >.

4b9b3361

Ответ 1

boost::variant будет выделять кучу, чтобы часть себя была рекурсивно определена как сама. (Он также будет выделяться в нескольких других ситуациях, неясно, сколько)

std::variant не будет. std::variant отказывается выделить кучу.

Нет никакого способа фактически иметь структуру, содержащую возможный вариант самого себя без динамического выделения, так как такая структура может быть легко показана бесконечной по размеру, если статически объявлено. (Вы можете кодировать целое число N, имея N рекурсий не одинаковых: буфер фиксированного размера не может содержать бесконечное количество информации.)

Таким образом, эквивалент std::variant хранит интеллектуальный указатель какого-то своего заполнителя самого рекурсивного экземпляра.

Это может работать:

struct s;
using v = std::variant< int, std::unique_ptr<s> >;
struct s
{
  v val;
  ~s();
};
inline s::~s() = default;

и не получив этого, попробуйте:

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;

Это означает, что клиентский код должен сознательно взаимодействовать с unique_ptr<s>, а не с struct s.

Если вы хотите поддержать семантику копирования, вам нужно написать value_ptr, который копирует, и предоставить ему эквивалент struct copy_s; для реализации этой копии.

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)...)};
}

Живой пример из value_ptr.