Ответ 1
Стирание стилей 101:
Шаг 1: создайте обычный (или полурегулярный тип только для перемещения), который скрывает детали.
struct exposed_type;
Этот класс предоставляет концепции, которые вы хотите поддерживать. Копирование, перемещение, уничтожение, равен, общий порядок, хеш и/или любые специальные концепции, которые необходимо поддерживать.
struct exposed_type {
exposed_type(exposed_type const&);
exposed_type(exposed_type&&);
friend bool operator<(exposed_type const&, exposed_type const&);
friend std::size_t hash(exposed_type const&);
// etc
};
Многие из этих концепций могут быть грубо сопоставлены с помощью метода чистого виртуального интерфейса в вашем текущем решении на основе наследования.
Создайте не виртуальные методы в вашем типе Regular, который выражает понятия. Копировать/назначить для копирования и т.д.
Шаг 2: Напишите помощник стирания типа.
struct internal_interface;
Здесь у вас есть чистые виртуальные интерфейсы. clone()
для копирования и т.д.
struct internal_interface {
virtual ~internal_interface() {}
virtual internal_interface* clone() const = 0;
virtual int cmp( internal_interface const& o ) const = 0;
virtual std::size_t get_hash() const = 0;
// etc
virtual std::type_info const* my_type_info() const = 0;
};
Сохраните интеллектуальный указатель 1 для этого в вашем стандартном типе выше.
struct exposed_type {
std::unique_ptr<internal_interface> upImpl;
Переслать регулярные методы помощнику. Например:
exposed_type::exposed_type( exposed_type const& o ):
upImpl( o.upImpl?o.upImpl->clone():nullptr )
{}
exposed_type::exposed_type( exposed_type&& o )=default;
Шаг 3: напишите реализацию стирания типа. Это класс template
, который хранит T
и наследует от помощника, и переводит интерфейс в T
. Используйте бесплатные функции (вроде как std::begin
), которые используют методы в реализации по умолчанию, если не была найдена свободная функция adl.
// used if ADL does not find a hash:
template<class T>
std::size_t hash( T const& t ) {
return std::hash<T>{}(t);
}
template<class T>
struct internal_impl:internal_interface {
T t;
virtual ~internal_impl() {}
virtual internal_impl* clone() const {
return new internal_impl{t};
}
virtual int cmp( internal_interface const& o ) const {
if (auto* po = dynamic_cast<internal_interface const*>(&o))
{
if (t < *po) return -1;
if (*po < t) return 1;
return 0;
}
if (my_type_info()->before(*o.my_type_info()) return -1;
if (o.my_type_info()->before(*my_type_info()) return 1;
ASSERT(FALSE);
return 0;
}
virtual std::size_t get_hash() const {
return hash(t);
}
// etc
std::type_info const* my_type_info() const {
return std::addressof( typeid(T) ); // note, static type, not dynamic
}
};
Шаг 4: добавьте конструктор к вашему регулярному типу, который принимает T
и создает из него реализацию стирания типа, и наполняет его своим умным указателем на помощника.
template<class T,
// SFINAE block using this ctor as a copy/move ctor:
std::enable_if_t<!std::is_same<exposed_type, std::decay_t<T>>::value, int>* =nullptr
>
exposed_type( T&& t ):
upImpl( new internal_impl<std::decay_t<T>>{std::forward<T>(t)} )
{}
После всей этой работы у вас теперь есть неинтрузивная полиморфная система с регулярным (или полурегулярным) типом значения.
Ваши функции factory возвращают правильный тип.
Посмотрите на примеры реализации std::function
, чтобы убедиться, что это сделано полностью.
1 как уникальные, так и общие - хорошие варианты, в зависимости от того, хотите ли вы хранить неизменяемые/копировать данные записи или вручную клонировать.