Как стать автором
Обновить

Комментарии 42

Велосипедисты, Сталин дал приказ!

Велосипедисты, зовёт Отчизна нас!

Обажаю подобные штуки для С++, спасибо.

Boost multi_index на порядок функциональнее и позволяет создавать множество индексов на данные не двигая в памяти сами данные. И главное - читый C++, никаких макросов.

Где вы видите двиганье данных? Индекс, это вектора указателей, разложенные по мапам или просто упорядоченные. Т.е. когда я пишу что можно работать с std::vector<Row*>, не меняя синтаксиса, то это это самое и будет.

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

А на счет multi_index, спасибо, позже посмотрю, что можно от-туда взять себе в пользование.

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

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

Что-нибудь типа такого? (навскидку):

#include <iostream>

struct Row
{
  int id;
  double value;
};

template<typename R, typename T>
auto &getMemberRef(const R &row, T member)
{
  return row.*member;
}

int main()
{
  Row item = {1, 2.2};

  std::cout << getMemberRef(item, &Row::id)
    << " " << getMemberRef(item, &Row::value) << std::endl;
}
Не, это не реализует ничего, кроме:
#include <iostream>

struct Row
{
  int id;
  double value;
};

int main()
{
  Row item = {1, 2.2};
  std::cout << item.id << " " << item.value << std::endl;
}

Зачем здесь прилепили getMemberRef это не понятно.

А вот если бы показали, как суммировать, сортировать, поиск значения. Как это все раздувается и обвешивается дополнительными конструкциями, и код становится ради кода, а не ради результата.

getMemberRef - это как пример того, что можно сделать вместо вашего генератора лямбд на макросах (то что у вас называется COLUMNS) чисто средствами языка. А в плане остального вам уже предлагали посмотреть на Boost Multi-index.

Так я и говорю, покажите как именно этот вариант примените хотя бы к std::sort, сколько это дополнительных строк возьмет. Ну или может на бустовских каких-нибудь функциях. Как вы будете суммировать такую колонку, и т.д. У меня же то же, не запрещается использовать свои лямбды без макросов, о чем я и писал.

Будет интересно посмотреть на обвес, когда вы попробуете написать поиск по двум колонкам.

UseCols::findFirst(table,
 [](auto& item) { return std::tuple(item.f1, item.f2); },
 2, 3);

Вот мой вариант, без макросов. А у вас как будет? Весь код не обязательно, только сам поиск, и объявления дополнительных функций, если таковые нужны. Но если доп.функции отсылаются к типу Row, то это прицеп раздувающий код, а не либу.

Например так:

template<typename... Ts>
auto createMemberRefAccessor(Ts... members)
{
  return [members...](auto &row) {
    return std::forward_as_tuple(row.*members...);
  };
}

UseCols::findFirst(table,
                   createMemberRefAccessor(&Row::id, &Row::value),
                   2, 3);

P.S. Я не касаюсь пока самих функций поиска, я просто хочу продемонстрировать, на что можно заменить макросы-генераторы, пользуясь исключительно средствами языка.

Очень спасибо. Добавлю такой вариант в копилку к этой либе. Будет альтернатива макросу. Чуть позже оформлю и добавлю.

А зачем вообще макросы?

Проблема не в макросах, проблема в головах. Одни любят придумать шаткие и сложнопонимаемые макросы — болезнь унаследованная от С. Другие видят эту болезнь в каждом макросе, и даже типовые #ifndef в хеадерах заменяют на #pragma once.

Одни могут и перочинным ножичком себя покалечить. Другие могут ходить с ружьем десятки лет, и не отстреливать себе ногу, но получая прибыль от этого большую, чем если бы ходили с перочинным ножиком.

Плохой пример. #pragma once же лучше в любом случае. А если можно без макроса, то вообще хорошо.

Добавлю. Ваш макрос выглядит как раз сложным. Я понимаю простой макрос как азерт или тест, когда надо только прилепить номер строчки.

У #pragma once свои проблемы. Во-первых, она не везде есть. Во-вторых, она не работает правильным образом, если включаемый файл имеет несколько имён (скажем через симлинки) и одна часть кода включает его под одним именем, а другая - под другим.

Мне кажется что если в компиляторе нету pragma once то он не из тройки и модерн С++ там тоже нету.

template<typename R, typename F1, typename F2, typename V1, typename V2>
auto& findFirst(const std::vector<R>& table, F1 f1, F2 f2, V1 v1, V2 v2)
{
    for (auto& i : table)
        if (i.*f1 == v1 && i.*f2 == v2) return i;
    return R{};
}

auto found = findFirst(table, &Row::id, &Row::value, 1, 7.0f);

Я так понял нужно предоставить генератор функций/лямбд которые принимали бы row и выдавали бы tuple со ссылками на перечисленные элементы этого row, вот выше пример как это сделать без макросов.

Именно. И тогда после того как сформирован tuple, отпадает необходимость отсылаться к частному типу из vector. И тогда возможность построения универсальных алгоритмов на этом существенно возрастает.
Этот вариант не совсем подходит под универсальные, он под фиксированное количество полей. Но его можно поменять на вариадичный, нужно будет сделать функцию сравнения вариадичной, и вызывать ее из такой findFirst. Непосредственно здесь ошибочка, возврат ссылки на локальное значение в случае не найденности.
возврат ссылки на локальное значение в случае не найденности
Да, проблемка. А как надо?
    static R value;
    return value;
}
Нужно возвращать итератор, т.е. тип decltype(list.begin()), и в случае не найденности list.end(). Если конечно не хотим копировать всю структуру строки в результат.

Или указатель на элемент списка, т.е. тип decltype(&*list.begin()), и в случае не найденности nullptr.

Ещё один вариант — вернуть std::optional<R&>

И на что будет ссылаться R& в случае не найденности? А если пользователь метода хочет использовать копирование для результата? Ссылки не переопределяются, и следовательно операторы копирования или перемещения работать не будут.

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

А почему оно куда-то должно ссылаться, если в случае "ненайденности" возвращаться будет пустой optional?


Для копирования же — да, нужен std::reference_wrapper.

Потому что пустой ссылка быть не может, такое не компилируется. Чуть ниже qw1 написал про это.
Может, вернёмся к старым добрым указателям? R* отличный тип для возврата )) Почему они сейчас не в моде…
вариант — вернуть std::optional<R&>
А можно пример?
std::optional<R&> op;
Не компилируется.

Верно, в стандарт не приняли такое. Есть boost::optional с поддержкой ссылок.

Вы слышали про std::sort который принимает любой компаратор? Про то что можно создавать ссылки на поля, типа
template<typename C, typename T>
auto Foo(T C::* value) {
return 10;
}

Зачем всё сделано на макросах?

Если Вы про макросы, то значит Вы не то увидели, что я хотел показать. Классический std::sort не принимает никаких указателей на поля. Я показал, какой должна быть функция сортировки, что бы туда можно было перечислить поля, без описания всего компаратора. В больших количествах сортировок по множеству полей, это очень утомительно прописывать, легко запутаться, что является источником ошибок.

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

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

Может вы хотя бы годболтом научитесь пользоваться? Или вы на глаз компилируете?

легко запутаться, что является источником ошибок

То ли дело макросы, там нет источников ошибок и всё просто, а главное производительно, современно и понятно

Заглянул в реализацию - шаблоны разеделены на хедеры и cpp, всё, это уже автоматически ifndr, не используются универсальные ссылки вообще

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

Это не отменяет того, что я до этого написал.
Макросы с короткими именами, типа CAT или FIELD — может в любой момент вылезти боком.

Я например несколько недоумевал, почему не компилируется такой код
enum class ITERATOR_RESULT { SUCESS, ERROR, EOF };
Оказалось, что ERROR и EOF опрелены макросами в windows.h
Да, такое бывает. Чуть позже позаменяю все короткие названия на гитхабе.

У меня бы члены подобного энума имели бы префикс «IR_». И от таких эксцессов защищает, и немного повышает читаемость и самодокументируемость кода.

В итоге так и сделал. Но это enum class, а не enum, поэтому префикс приходится дважды указывать.

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


Как уже правильно заметил qw1, тут префикс будет просто повторяться дважды, соответственно читаемость и самодокументируемость никак не улучшит.

Да много где такой code-style, что enum members — all caps.

Знаю, что практика распространённая, но считаю её вредной — мало того, что вот таким неприятным конфликтам имён способствует, так ещё и привлекает излишнее внимание к совершенно безобидному использованию enum'ов.

много где такой code-style, что enum members — all caps

Любителей такого стиля можно посылать сюда, с аргументом «it's in the book»:
ES.9: Avoid ALL_CAPS names
Reason Such names are commonly used for macros. Thus, ALL_CAPS name are vulnerable to unintended macro substitution.
10 лет прошло. Оперативненько.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории