LINUX.ORG.RU

Обобщенные методы

 , обобщенные типы,


0

2

Предположим у меня есть некоторый метод foo, который на вход принимает коллекцию каких-то значений. Причём это может быть и std::vector и std::array и std::array_view, например. Должен ли я писать перегруженную версию метода для каждого из этих типов или можно указать «Принимай тип однородной коллекции данных с произвольным доступом, не важно какой, лишь бы удовлетворяло этому условию»? В рамках стандарта до С++17 включительно.

Всем спасибо за ответы.

Можно написать один метод для всех, если доступ и пр будет для всех одинаковый. Если нет - можно тоже обойтись одним методом + некоторое число оберток. В целом зависит от задачи, для каких экзотических вещей мб проще будет писать свою специализацию для каждого типа коллекции.

AntonI ★★★★★
()

Должен ли я писать перегруженную версию метода для каждого из этих типов

Это будет одним из признаков что Вы что-то делаете не так.

bugfixer ★★★★★
()

Предположим у меня есть некоторый метод foo, который на вход принимает коллекцию каких-то значений. Причём это может быть и std::vector и std::array и std::array_view, например.

У меня в API для работы с метаданными этот вопрос тривиален, так как имеется API, для работы с элементами из любого типа коллекции.

Поэтому то никакие template, специализации, ... просто не нужны.

Forum0888
()
Последнее исправление: Forum0888 (всего исправлений: 1)

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

писать ручками специализации не надо, для этого и сделаны темплейты.

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от alysnix

Ну вот по сути же те же array и vector одни и те же типы представляют - линейную коллекцию данных с произвольным доступом: у них и операторы общие для этого есть и методы присутствуют, так что мне даже код переделывать в реализации не придется, надо только правильно обобщить и вывести тип.

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

Ну вот по сути же те же array и vector одни и те же типы представляют - линейную коллекцию данных…

компилятор должен сгенерировать код вызова функций - например push_back() этих коллекций. но это разные функции для разных коллекций. они они только внешне выглядят одинаково. и поскольку он не понимает - какой обьект будет подставлен, он не знает какую функцию вызывать. то есть такие трюки не могут быть обрабаны компилятором. потому такие классы, с внешне одинаковыми функцяими не являются совместимыми.

но если будет шаблон, то компилдятор вычислит тип подставленного параметра и сгенерит код шаблона для данного типа, и будет уже знать - чей push_back в нем вызывать.

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

alysnix ★★★
()
Последнее исправление: alysnix (всего исправлений: 1)
Ответ на: комментарий от LongLiveUbuntu

Ну вот по сути же те же array и vector одни и те же типы представляют - линейную коллекцию данных с произвольным доступом

Если все коллекции contiguous, то можно сделать аля span - указатель и размер. Если нет, то тоже можно накостылить, например:

void f(function<bool(int&)> it) {
	int val;
	while (it(val))
		cout << val << endl;
}

int main() {
	list<int> l{1,3,5,6};
	auto lit = l.begin();
	auto list_it = [&](int &val) {
		if (lit != l.end()) {
			val = *lit;
			++ lit;
			return true;
		}
		return false;
	};
	f(list_it);

	vector<int> v{7,8,9};
	auto vit = v.begin();
	auto vec_it = [&](int &val) {
		if (vit != v.end()) {
			val = *vit;
			++ vit;
			return true;
		}
		return false;
	};
	f(vec_it);
}

Я хз насколько это действительно нужно, ну может речь о каком-то api, которое торчит в внешний мир и не хочется плодить шаблоны.

kvpfs ★★
()

Лучше принимать на вход только array_view и работать только с ним. Насколько я понимаю, это микрософтовский аналог std::span/gsl::span. Если он умеет приводиться к span, то лучше использовать последний, т.к. он вошёл в стандарт и переносимость выше. И насколько я понял, array_view умеет конструироваться и из array, и из vector.

Альтернативно можно заморочиться с итераторами, проверять их категории и т.д., но по идее всё это уже сделано в array_view/span.

Ну и на всякий случай, никогда не принимай array_view, span, string_view по ссылке. Только как копию.

Ivan_qrt ★★★★★
()

Из своего опыта и привычек, я бы посоветовал писать обобщенный код на итераторах. Затем его можно обмазать адаптерами: обобщенным foo(T v) {foo(v.begin(), v.end());} и специализациями для нетривиальных типов.

filosofia
()
Ответ на: комментарий от kvpfs

тут list_it и vec_it, имеют физически разный код(несмотря на внешнюю одинаковость в текстовом виде), и по сути являются ручной имплементацией некоего шаблона. то есть вы не ушли от шаблонов, а реализовали шаблон доступа к элементу коллекции ручками.

alysnix ★★★
()
Ответ на: комментарий от kvpfs

Так я и хочу это на шаблонах сделать, чтобы меньше определений плодить.

Было бы это в случае Скалы, я бы вообще не парился, а просто передавал Seq[A] как общий тип для последовательных коллекций во всей иерархии. Но в С++ array - это не vector, черт их дери, хотя оба про одно и тоже как концептуально, так и в реализации.

Поэтому и спрашиваю как малой кровью можно максимально точно обобщить передаваемый тип и отследить, что это именно допустимый для передачи тип. Про static_assert уже знаю.

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от LongLiveUbuntu

Поэтому и спрашиваю как малой кровью можно максимально точно обобщить передаваемый тип и отследить, что это именно допустимый для передачи тип. Про static_assert уже знаю.

+ конецпты, там же и примеры + всякие same_as из <type_traits> и <concepts>

kvpfs ★★
()
Последнее исправление: kvpfs (всего исправлений: 2)
Ответ на: комментарий от LongLiveUbuntu

Поэтому и спрашиваю как малой кровью можно максимально точно обобщить передаваемый тип и отследить, что это именно допустимый для передачи тип. Про static_assert уже знаю.

А зачем? Если у типа есть begin/end и нужный вам operator[], то код в вашем шаблоне функции будет работать. Если чего-то нет, то компилятор выдаст ошибку.

Или вам принципиально, что данные идут последовательно один элемент за другим (например, для передачи указателей на содержимое вектора в функции, подобные memchr, memcpy)?

eao197 ★★★★★
()
Ответ на: комментарий от LongLiveUbuntu

Было бы это в случае Скалы, я бы вообще не парился, а просто передавал Seq[A] как общий тип для последовательных коллекций во всей иерархии.

не знаю как реализована там scala внутри, но в плюсах это делается реализацией общего интерфейса разными коллекциями. стандартные контейнеры плюсов реализовованы проще и эффективней, но за счет прямой совместимости. но вам ничто не мешает нарисовать свои коллекции, реализующие общий интерфейс, на стандартных контейнерах. и получится как в скала. если оно вообще кому-то нужно.

alysnix ★★★
()
Ответ на: комментарий от LongLiveUbuntu

Поэтому и спрашиваю как малой кровью можно максимально точно обобщить передаваемый тип и отследить, что это именно допустимый для передачи тип.

Как в Scala обобщить не выйдет, у каждого способа (тут уже расписали все) свои плюсы и минусы.

snizovtsev ★★★★★
()

Тут главный вопрос - ценой чего ты хочешь достигнуть цели?

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

Можно ещё как-то извратиться. Но я бы посоветовал написать шаблонный метод и не страдать ерундой.

pathfinder ★★★★
()
Ответ на: комментарий от filosofia

Собственно да. Стандартное решение задачки передачи коллекции это использование пары итераторов [begin, end). Так вся STL сделана. Вот, например std::vector::insert, четвертая перегрузка.

dvetutnev
()
Последнее исправление: dvetutnev (всего исправлений: 1)
Ответ на: комментарий от LongLiveUbuntu

Но в одних местах туда передается array, а в других - vector

Проблема-то в чем? На всякий случай уточню, что подразумевается

template <typename Iter, typename Sentinel>
void foo(Iter begin, Sentinel end);

а не указание конкретных std::vector<A>::iterator или std::array<B>::iterator.

Siborgium ★★★★★
()
Последнее исправление: Siborgium (всего исправлений: 1)

Должен ли я писать перегруженную версию метода для каждого из этих типов или можно указать «Принимай тип однородной коллекции данных с произвольным доступом, не важно какой, лишь бы удовлетворяло этому условию»?

ничего такого не надо делать.

void foo(auto collection) { ... }

компилятор даст знать, если не сможет собрать это для какого-то типа. В C++20 есть std::random_access_iterator для явной проверки на произвольный доступ во время компиляции, но это уже лирика.

Lrrr ★★★★★
()
Ответ на: комментарий от Siborgium

Сейчас уже можно по-простому:

#include <ranges>

template <std::ranges::forward_range R>
void foo(R const & r);

И суй туда, что угодно, vector, array, list, итд итп.

rupert ★★★★★
()
Последнее исправление: rupert (всего исправлений: 1)
Ответ на: комментарий от Siborgium

более старыеубогие стандарты

Эта поразительная ситуация наблюдается только в C++. Мазохизм какой-то. Даже целые книги пишут, как доставить себе максимальное количество боли, сидя на старых стандартах.

rupert ★★★★★
()
Ответ на: комментарий от rupert

Соболезную, но у нас пока только C++17 максимум. Это еще ничего, не так давно я работал в организации, где был доступен только C++98. Вот и крутись как хочешь, хорошо Qt относительно свежий был.

LongLiveUbuntu ★★★★★
() автор топика
Ответ на: комментарий от rupert

Эта поразительная ситуация наблюдается только в C++. Мазохизм какой-то.

В Javascript тоже самое, не только в С++. По крайней мере для тех, кто заботится о том чтобы запускалось в старых браузерах.

fsb4000 ★★★★★
()

Причём это может быть и std::vector и std::array и std::array_view, например.

для всего этого делаешь range на итераторах

template<typename Iterator>
  std::string joinRange(Iterator it1, Iterator it2)
anonymous2 ★★★★★
()
Последнее исправление: anonymous2 (всего исправлений: 1)
4 июля 2023 г.