Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[3.144.99.39] |
|
Страницы: (2) [1] 2 все ( Перейти к последнему сообщению ) |
Сообщ.
#1
,
|
|
|
Всем привет!
Вопрос конечно уже поднимался. Простое в лоб решение - делать это с помощью векторов-векторов-...-векторов-типов. Но я вот задумался, а что если многомерность "спроецировать" на одномерный вектор? Даст ли это какие-то преимущества в каких-либо случаях, да и вообще, "стоит ли овчинка выделки"? На сколько я где-то видел, пролетала инфа, что в последней версии стандарта С++ дали возможность в операторе [] использовать не один аргумент, а по количеству от нуля и более. Таким образом, теперь должно быть возможным выражение типа: value = vector[2,3,4]; // что эквивалентно value = vector.operator[](2,3,4); Т.е. смотрится вполне себе более естественно, нежели вызов какой-нить вида: value = vector.at(2,3,4); У меня нет компилятора с последним стандартом, но хотелось бы поэкспериментировать с метапрограммированием, где постараться реализовать (если это возможно будет): 1) Менять количество размерностей в рантайме 2) В операторе[] попробовать задавать переменное количество аргументов для последующего использования с учетом п.1 (если последний стандарт это разрешит) Т.е. чистая "синтетика", для проверки этих возможностей. Да, конечно, в будущем можно попробовать реализовать что-то более практическое, например срезы. Qraizer, чё скажешь? Стоит этим заморачиваться? И если "да", сможешь накидать простенький шаблонный класс для п.1-п.2? |
Сообщ.
#2
,
|
|
|
То, о чём ты говоришь, планируется только в C++23 который ещё не ратифицирован. На данный момент эту фичу поддерживают лишь GCC и CLang. И да, если рассматривать весь аспект твоего поста, то это чисто сахар. В рантайме ты можешь поменять размерность, но на лету сменить синопсис [] не выйдет, для этого нужна была бы соответствующая перегрузка, а параметры по умолчанию, поверь, очень плохая идея в этом контексте. Ты можешь накидать в {} какой-нибудь std::initializer_list, но тогда тебе не требуется много операндов, и такое ты и сейчас можешь сделать. Срезы потребуют от тебя промежуточных классов, типа "строка", "столбец" и "слой" в 3D объекте, но это ты и сейчас можешь. И так и делают со времён C++98. И более того, в многооперандном [] тут вообще нужны не будет, он тупо ничем не поможет.
В целом, я не вижу выгод от применения этой фичи. Замена [][][]... на [,,,...] сугубо косметическая. Думаю, её приняли исключительно из-за её дешевизны в реализации, и малого риска поломать старый код. Хотя, если кто юзал operator,() между [], того ждёт неприятный сюрприз. В частности, это все автоматизированные средства инструментирования кода. |
Сообщ.
#3
,
|
|
|
Цитата Qraizer @ В рантайме ты можешь поменять размерность, но на лету сменить синопсис [] не выйдет Вот вот поэтому и вопрос тебе задал! Можно будет делать что-то в виде (это вопрос): T& operator[] (size_t Idx1, ...) Иными словами. В зависимости от текущей размерности - указывать разное количество индексов в рантайме. |
Сообщ.
#4
,
|
|
|
Формально да, но вот как ты её будешь реализовывать, это вопрос. У тебя будет компайл-тайм генерация оператора, и что ты будешь делать, если его вариадик не совпадает с текущей размерностью, определяемой в ран-тайм? Бросить исключение – самый разумный вариант. ИМХО через initializer_list будет не сложнее, и в производительности не потеряешь, всё равно проверки делать.
|
Сообщ.
#5
,
|
|
|
Цитата Qraizer @ ИМХО через initializer_list будет не сложнее, и в производительности не потеряешь, всё равно проверки делать. Да, скорее всего, да не - точно ты прав. Я насчёт initializer_list. Я вот попробовал на старых стандартах провести "Операцию Ы" (кристально чистая синтетика), получается, но увы, не так красиво. Один вопрос пока не знаю - в новейшем стандарте можно ли в operator[] объявлять переменное число аргументов (не вариардик, а именно Си-стайл рантайма): #include <iostream> #include <cstdarg> using namespace std; template<typename T> class SimpleClass { int dim = 0; int ret = 0; public: T& operator_bl(size_t Dim,...) { if (Dim != dim) throw std::range_error("Dimension Error"); ret = 0; va_list list; va_start(list, Dim); for(int i=0; i<Dim; i++) { ret += va_arg(list, T); } va_end(list); cout << "Get: " << Dim << ", Ret: " << ret << endl; return ret; } void setDimension(size_t Dim) { dim = Dim; } }; int main() { try { SimpleClass<int> value; value.setDimension(1); value.operator_bl(1, 11); value.setDimension(2); value.operator_bl(2, 22, 33); value.operator_bl(3, 1, 1, 1); } catch (std::exception &err) { cout << "Ваапще Лев Лещенко: " << err.what(); } catch (...) { cout << "Случилось штото страшное!"; } return 0; } Get: 1, Ret: 11 Get: 2, Ret: 55 Ваапще Лев Лещенко: Dimension Error Online there Ну а вообще "да", нововведения в планируемом стандарте относительно operator[] меня радуют. Для многомерных массивов с проекцией на одномерный, но с неизменяемой размерностью - меня радуют! Последний вопрос к тебе по этой теме. Реализация многомерности в виде "проекции" на одномерный вектор - чем-то хороша, или это просто очередной велосипед с квадратными колесами? |
Сообщ.
#6
,
|
|
|
Формально тебе никто не запрещает написать
template <typename ...U> requires(std::same_as<U, std::size_t> && ...) auto operator[](U... idx) { return operator_bl(sizeof...(U), idx...); } Добавлено Цитата Majestio @ Когда-то кто-то придумал одним махом получать память сразу под весь массив. Ну вот то ли недопёр до циклов с malloc(), то ли ему были их лень писать. Но почему-то было не лень руками считать индексы. Вот и всё, что могу сказать по этой теме.Реализация многомерности в виде "проекции" на одномерный вектор - чем-то хороша, или это просто очередной велосипед с квадратными колесами? Единственный профит может быть в том, что единый кусок памяти с большей вероятностью будет лежать в одном кешированном регионе памяти, чем множество блочков поменьше от разных malloc(). Но окупится это лишь в специфических алгоритмах работы с таким массивом. |
Сообщ.
#7
,
|
|
|
Цитата Qraizer @ Когда-то кто-то придумал одним махом получать память сразу под весь массив. Ну вот то ли недопёр до циклов с malloc(), то ли ему были их лень писать. Но почему-то было не лень руками считать индексы. Вот и всё, что могу сказать по этой теме. Единственный профит может быть в том, что единый кусок памяти с большей вероятностью будет лежать в одном кешированном регионе памяти, чем множество блочков поменьше от разных malloc(). Но окупится это лишь в специфических алгоритмах работы с таким массивом. Да, возможно еще наверное некоторое ускорение при предварительном резервировании - выделение памяти все же операция относительно не быстрая. Одним участком это гораздо быстрее, чем в циклах мелкими. |
Сообщ.
#8
,
|
|
|
Цитата Qraizer @ Хотя, если кто юзал operator,() между [], того ждёт неприятный сюрприз. Операция запятая внутри [] по идее будет объявлена как deprecated. |
Сообщ.
#9
,
|
|
|
С чего бы? Много чести. В конце концов это затронет только перегруженные [], которые спецом проектируются под такое использование. Я не читал Стандарт на этот счёт, возможно там есть исключение для токенизаторов, требующее от них переходить к старой семантике , в случае отсутствия подходящих перегрузок. Но даже если и нет, для перегруженных &&, || и , и так есть семантическое исключение: они не вносят точки следования. И даже для >> есть: эта хрень не должна рассматриваться как арифметическая операция в контексте списка аргументов шаблонов. Есть и ещё примеры посложнее, с typename например.
|
Сообщ.
#10
,
|
|
|
Цитата Qraizer @ С чего бы? Много чести. Где-то прочел, сорян, не запомнил где. |
Сообщ.
#11
,
|
|
|
Говоря об автоматизированных средствах инструментирования кода. Вот смотри. У нас есть код типа такого:
if (a == b) x = y; else z = t; if (a == b){StartBlock(tag1); x = y; EndBlock(tag1);} else{StartBlock(tag2); z = t; EndBlock(tag2);} if (a == b) x = y; if (AnalyzeDecision(tag1, a == b)){StartBlock(tag1); x = y; EndBlock(tag1);} else {StartBlock(tag2); EndBlock(tag2);} Усложним входные данные. Нужно собрать покрытие условий. Представим себе решение, чуточку посложнее: if (a == b+d || c == 123) x = y; else z = t; if (AnalyzeCondition(tag1, a == b+d) || AnalyzeCondition(tag2, c == 123)) {StartBlock(tag1); x = y; EndBlock(tag1);} else {StartBlock(tag2); z = t; EndBlock(tag2);} Но вот вдруг нужно собрать покрытие MCC. Здесь учитываются не только все условия в решении, но и каждый вход в эти условия: if (AnalyzeCondition(tag1, AnalyzeEntry(tag1, a) == AnalyzeEntry(tag2, b)+AnalyzeEntry(tag3, d)) || AnalyzeCondition(tag2, AnalyzeEntry(tag4, c) == 123)) {StartBlock(tag1); x = y; EndBlock(tag1);} else {StartBlock(tag2); z = t; EndBlock(tag2);} unsigned __dec, __cond, __eval, __mask; /* ... */ if (( __dec = FindDecision(tag1), __cond = FindCondition(__dec, tag1), __eval = a == b+d, __mask = RegisterEntries4(__dec, __cond, a, b, d, c, __eval), CheckCondition(__dec, __cond, __mask), __eval ) || ( __cond = FindCondition(__dec, tag2), __eval = c == 123, __mask = RegisterEntries4(__dec, __cond, a, b, d, c, __eval), CheckCondition(__dec, __cond, __mask), __eval ) ) {StartBlock(tag1); x = y; EndBlock(tag1);} else{StartBlock(tag2); z = t; EndBlock(tag2);} Эту операцию инструментаторы очень любят как раз за её странные качества: вычисление и игнор левого операнда и внесение точки следования перед вычислением правого, и всё это в пределах одного выражения. Сразу предупреждаю, что тут только демонстрация концепций. Реальные инструментаторы могут их реализовывать по-разному. Например, RTRT для MC/DC и MCC массивы условий/решений для приведённого выше выражения (a == b+d || c == 123) формирует в виде строк типа "XXXX", "1111", "1113" и "333X", а вместо функций использует макросы. Там вообще жесть разобраться с нагенеренным, если вдруг приспичит. Но настоящее веселье начинается, когда инструментатор сталкивается с ?: . Интересно же, как инструментатор выкрутится на: *(a == b ? &x : &z) = (a == b ? y : t); unsigned __tag; decltype(0 ? &x : &z) __res1; decltype(0 ? y : t) __res2; /* ... */ *(__res1 = (AnalyzeDecision(tag1, a == b) ? (__tag = tag1, StartBlock(__tag), &x) : (__tag = tag2, StartBlock(__tag), &z)), EndBlock(__tag), __res1) = (__res2 = (AnalyzeDecision(tag2, a == b) ? (__tag = tag3, StartBlock(__tag), y) : (__tag = tag4, StartBlock(__tag), t)), EndBlock(__tag), __res2); P.S. Что такое tag и с чем их есть. Это просто тэги, навешанные инструментатором на точки наблюдения и собранные в статические базы, когда он анализирует сырцы и генерит инструментированный код. Формально это просто числа, но не всегда, иногда это могут быть целые структуры с кучей доп.инфы. Для них главное быть уникальными и точно идентифицировать точку в коде. Тот факт, что они у меня тут нередко совпадают для разных точек... ну, это смущает, но не противоречит, т.к. тэги для блоков, решений, условий и входов собираются в разные базы и анализируются разными функциями, так что их назначение не пересекается, даже когда они численно равны. P.P.S. И да, это всё C. А вот с Плюсами гораздо интереснее. У него есть ссылки, нетривиальные классы, которые нельзя вот так просто взять и посохранять и покопировать, шаблоны, для которых решения в одной конкретизации совсем не то же решение, что в другой конкретизации этого же шаблона. Но самый смак – это исключения. Они вносят настолько иногда неожиданные потоки управления, что диву даёшься, как это всё инструментаторы умудряются учитывать. Добавлено Это всё к слову о , внутри []. Мне как-то что-то по голове стукнуло, и я решил замутить сортировку без ветвлений. Там и не только , в индексации была. |
Сообщ.
#12
,
|
|
|
Цитата Qraizer @ Говоря об автоматизированных средствах инструментирования кода. Цитата Qraizer @ Не буду подробно это комментировать, должно быть и так понятно. Если кто не понял, мы сейчас о сборе структурного покрытия уровней SC, DC и MC/DC. Но вот вдруг нужно собрать покрытие MCC. Здесь учитываются не только все условия в решении, но и каждый вход в эти условия: Концептуально понятно. Но это все же специфическая область разработки. Пришлось почитать это, тогда стало понятнее "зачем". Ну и тут немного (10 стр). Увы, мне пока это не интересно, для своих задач не вижу этому прикладного применения. Но для расширения кругозора - годно. Чисто ради интереса ... что будет с разбором вот такой конструкции этими средствами: if ( (a == 1 || a > 0) && b > 1) // ... Тут явно одно из условий лишнее, будет ли оно выкинуто в процессе последующей компиляции? |
Сообщ.
#13
,
|
|
|
Цитата Majestio @ Высококритичное ПО без этого не обходится никак. Но в целом если... если ты когда-нибудь пользовался чем-то типа profile-guided optimization или просто смотрел в среде твоего отладчика, какими путями ты сюда попал (и какие пути вообще были задействованы, это довольно распространённый сервис), то ты однозначно пользовался инструментированием кода. А также если когда использовал расширенный профайлинг для чего-то очень точного. Но это все же специфическая область разработки. |
Сообщ.
#14
,
|
|
|
Цитата Qraizer @ Высококритичное ПО без этого не обходится никак. Но в целом если... если ты когда-нибудь пользовался чем-то типа profile-guided optimization или просто смотрел в среде твоего отладчика, какими путями ты сюда попал (и какие пути вообще были задействованы, это довольно распространённый сервис), то ты однозначно пользовался инструментированием кода. А также если если использовал расширенный профайлинг для чего-то очень точного. Увы, высококритичное ПО не доводилось писать. Как правило UI+DB |
Сообщ.
#15
,
|
|
|
Цитата Majestio @ Оптимизатор его теоретически может выкинуть. Однако инструментатору пофигу, он не оптимизатор, поэтому проинструментирует то, что видит. И после этого оптимизатору уже не отвертеться, вызовы функций из-под сайд-эффектов его рвение однозначно остановят. Так что получишь непокрытие, которого в неинтрументированном коде могло не быть. Но если ты считаешь это недостатком, то нет, сбор покрытия как раз и полезен для выявления мёртвых фрагментов. Тут явно одно из условий лишнее, будет ли оно выкинуто в процессе последующей компиляции? Добавлено Цитата Majestio @ В том-то и засада, что это хоть и маловосстребованный сегмент рынка ПО, но очень дорогой и безумно ответственный. Так что использование различных автоматизированных средств там приветствуется (конечно, далеко не так всё просто ) хотя бы потому, что они дают детерминированный результат, в отличие от человека, которому, ну, свойственно. высококритичное ПО не доводилось писать Добавлено Цитата Qraizer @ А впрочем не получишь. Лишнее-нелишнее, но покрыть по MC/DC там всё можно. Но вот если б ты написалТак что получишь непокрытие, которого в неинтрументированном коде могло не быть. if ( (a > 0 || a == 1) && b > 1) // ... |