альтернатива array_view для карт, наборов и т. д.

Давайте предположим, что у меня есть некоторая иерархия классов, которая имеет пару virtual функции, возвращающие ссылку на контейнер:

#include <vector>
#include <set>
#include <map>
#include <unordered_set>
#include <unordered_map>

class Interface {
public:
virtual const std::vector<int>& getArray() const = 0;
virtual const std::set<int>& getSet() const = 0;
virtual const std::map<int, int>& getMap() const = 0;
};

class SubclassA : public Interface {
public:
const std::vector<int>& getArray() const override { return _vector; }
const std::set<int>& getSet() const override { return _set; }
const std::map<int, int>& getMap() const override { return _map; }

private:
std::vector<int> _vector;
std::set<int> _set;
std::map<int, int> _map;
};

На данный момент возможно только вернуть vector, set, или же map в любом подклассе Interface учебный класс. Однако для vector часть, я мог бы использовать, например, gsl::array_view чтобы смягчить это ограничение:

class Interface {
public:
virtual gsl::array_view<const int> getArray() const = 0;
virtual const std::set<int>& getSet() const = 0;
virtual const std::map<int, int>& getMap() const = 0;
};

class SubclassA : public Interface {
public:
gsl::array_view<const int> getArray() const override { return _vector; }
const std::set<int>& getSet() const override { return _set; }
const std::map<int, int>& getMap() const override { return _map; }

private:
std::vector<int> _vector;
std::set<int> _set;
std::map<int, int> _map;
};

class SubclassB : public Interface {
public:
gsl::array_view<const int> getArray() const override { return _array; }
//    const std::set<int>& getSet() const override { return _set; }
//    const std::map<int, int>& getMap() const { return _map; }

private:
std::array<int, 3> _array;
std::unordered_set<int> _set;
std::unordered_map<int, int> _map;
};

Так что вопрос в том, есть ли альтернатива array_view для использования с другими типами контейнеров? По сути, все, что я хотел бы иметь, — это легкий объект, который я мог бы вернуть из функции, которая бы действовала как неизменяемое представление для некоторого контейнера, без указания конкретного типа контейнера. Для меня было бы даже целесообразно засунуть std::set к чему-то вроде array_view, но с меньшим количеством поддерживаемых операций (например, без произвольного доступа). map явно другой зверь и потребует другого view поддержка ассоциативного поиска, но даже для map Я думаю, что было бы полезно иметь возможность сказать array_view<const std::pair<const int, int>>, Я спрашиваю слишком много? Или, может быть, есть разумные способы реализовать это? Или, может быть, существуют даже реализации таких «представлений»?

PS: наследование не является обязательным условием — я просто подумал, что это самый простой способ представить проблему.

4

Решение

Если вы просто ищете диапазон со стертым шрифтом, вы можете проверить boost::any_range:

using IntRange = boost::any_range<
int,
boost::forward_traversal_tag,
int,
std::ptrdiff_t>;

int sum(IntRange const& range) {
return std::accumulate(range.begin(), range.end(), 0);
}

int main()
{
std::cout << sum(std::vector<int>{1, 2, 3}) << std::endl;  // OK, 6
std::cout << sum(std::set<int>{4, 5, 6}) << std::endl;     // OK, 15
}

Даже когда вы пытаетесь использовать его неправильно:

sum(std::map<int, int>{})

сообщение об ошибке не страшно:

/usr/local/include/boost/range/detail/any_iterator_wrapper.hpp:40:60: error: invalid static_cast from type 'std::pair<const int, int>' to type 'int&'
return static_cast<Reference>(const_cast<T&>(x));
^

Вы можете создать псевдоним для вашего варианта использования:

template <typename T>
using FwdImmutableRangeT = boost::any_range<T,
boost::forward_traversal_tag,
const T&, std::ptrdiff_t>;

И вернуть те:

class Interface {
public:
virtual FwdImmutableRange<int> getArray() const = 0;
virtual FwdImmutableRange<const int> getSet() const = 0;
virtual FwdImmutableRange<std::pair<const int, int>> getMap() const = 0;
};
4

Другие решения

Я не знаю ни одного, но общий вид для данного набора интерфейсов не так сложно написать. Вы бы использовали стирание типа с vtable вручную, чтобы сохранить вещи без выделения.

Сохраните pvoid и указатель на таблицу функций. Каждая функция стирает зависимость типа одной операции.

Если операция имеет подпись R(Args...) тогда указатель функции стирания в таблице имеет подпись R(void*, Args...), Я использую лямбды без сохранения состояния для записи их в фабрику виртуальных таблиц, которая создает статическую локальную виртуальную таблицу и возвращает указатель на постоянную виртуальную таблицу.

Пользовательский класс предоставляет операции, перенаправляя их в vtable. У него есть шаблон ctor, который сохраняет pvoid переданным значением и получает специфичный для типа vtable из фабрики.

Вы должны быть осторожны с вашими копорами / переносами в вашем классе представления: шаблон ctor должен защищать SFINAE от принятия экземпляров класса представления.

Раздражает то, что вам нужно либо определить новую семантику для работы ассоциативных контейнеров, либо вам нужно также стереть их итераторы. И в справедливых представлениях, поскольку итераторы, как предполагается, подобны значению. Это большое преимущество вектора, потому что T* можно использовать вместо!

Теперь, когда я думаю об этом, boost имеет стертые итераторы и, возможно, представления ассоциативных контейнеров.

Если нам просто нужна функциональность «это там» (и «что это» для карты), и вам не нужна итерация, это довольно просто:

namespace details {
template<class K>
using exists = bool(*)(void*, K const&);
template<class K, class V>
using get = V(*)(void*, K const&);

template<class T>
struct setlike_vtable {
exists<T> pexists = 0;
template<class S>
static void ctor( setlike_vtable* table ) {
table->pexists = [](void* p, T const& k)->bool {
S*ps = static_cast<S*>(p);
return ps->find(k) != ps->end();
};
}
template<class S>
static setlike_vtable const* make() {
static const setlike_vtable retval = []{
setlike_vtable retval;
ctor<S>(&retval);
return retval;
}();
return &retval;
}
};
template<class K, class V>
struct maplike_vtable : setlike_vtable<K> {
get<K,V> pget = 0;
template<class S>
static void ctor( maplike_vtable* table ) {
setlike_vtable<K>::template ctor<S>(table);
table->pget = [](void* p, K const& k)->V {
S*ps = static_cast<S*>(p);
return ps->find(k)->second;
};
}
template<class S>
static maplike_vtable const* make() {
static const maplike_vtable retval = []{
maplike_vtable retval;
ctor<S>(&retval);
return retval;
}();
return &retval;
}
};
}

template<class T>
struct set_view {
details::setlike_vtable<T> const* vtable = 0;
void* pvoid = 0;
template<class U,
std::enable_if_t<!std::is_same<std::decay_t<U>, set_view>{}, int> =0
>
set_view(U&& u):
vtable( details::setlike_vtable<T>::template make<std::decay_t<U>>() ),
pvoid( const_cast<void*>( static_cast<void const*>( std::addressof(u) ) ) )
{}
set_view(set_view const&)=default;
set_view() = default;
~set_view() = default;
set_view& operator=(set_view const&)=delete;
explicit operator bool() const { return vtable; }

bool exists( T const&t ) const {
return vtable->pexists( pvoid, t );
}
};
template<class K, class V>
struct map_view {
details::maplike_vtable<K, V> const* vtable = 0;
void* pvoid = 0;
template<class U,
std::enable_if_t<!std::is_same<std::decay_t<U>, map_view>{}, int> =0
>
map_view(U&& u):
vtable( details::maplike_vtable<K,V>::template make<std::decay_t<U>>() ),
pvoid( const_cast<void*>( static_cast<void const*>( std::addressof(u) ) ) )
{}
map_view(map_view const&)=default;
map_view() = default;
~map_view() = default;
map_view& operator=(map_view const&)=delete;
explicit operator bool() const { return vtable; }

bool exists( K const&k ) const {
return vtable->pexists( pvoid, k );
}
V get( K const& k ) const {
return vtable->pget( pvoid, k );
}
};

обратите внимание, что вы хотите map_view< Key, Value const& > как правило, если вы не хотите get вернуть по стоимости.

живой пример.

Итерация через посещение проста, но требует, чтобы переданный посетитель был стерт типом (скажем так std::function). Итерация через итераторы требует итераторов со стертым типом, а итераторы со стертым типом должны иметь семантику значений. В этот момент вы лучше всего крадете boostРеализация.

Сопрограммы, предлагаемые прямо сейчас, дают альтернативный способ решения проблемы; пусть стертые по типу представления используют сопрограммы для реализации перечисления вместо посещения.

Могу поспорить, что вышеупомянутый вид немного быстрее, чем boost::any_range, так как он имеет меньше работы из-за дизайна. Вы можете ускорить его, переместив виртуальную таблицу, чтобы она была встроенной в теле представления, удалив промах кэша; для стирания больших типов это может вызвать вздутие памяти во время выполнения, но в представленных выше представлениях стирания типов в vtable хранятся 1-2 указателя. Указатель на 1-2 указателя кажется глупым.

1

По вопросам рекламы [email protected]