Comments 35
В С++20 должна появится возможность писать так.
По моему эта возможность появилась в C++17
if (auto res = "true" *in* some_map)
Для этого придётся сделать глобальную переменную in, зато она уже нормально включается в namespace. Понятно, что сама она состояние хранить не сможет, нужно на первом умножении создавать временное значение и хранить ключ там.
он имеет каст к bool. преследует как раз safe bool idiome и нужен как раз для таких случаев
godbolt.org/g/Bz35nQ
Хм… Странный подход. Сейчас, вроде, в C++ наметился тренд в сторону функциональной нотации. То есть не key in map
, а contains(map, key)
например. Но это ладно, это мелочи. В предложенном методе нарушается подход Single Responsibility. Это как если бы в std::stack был метод pop_and_get (например), или pop не только бы извлекал элемент со стека, но и возвращал бы его. Но такого метода нет (можно у Саттера почитать, почему) — и это правильно. Так же и тут: зачем в одном операторе совмещать две разных ответственности? Оператору in оставить возвращаемое значение bool. А то, что хочется, реализуется чуть проще:
template<typename M, typename K>
std::optional<typename M::const_iterator> from(const M& m, K&& key)
{
auto p = m.find(key);
if (p != m.end())
return p;
return std::optional<typename M::const_iterator>();
}
//...
if (auto res = from(some_map, "cat"))
{
std::cout << res->second << std::endl;
}
Можно даже optional на значение возвращать, чтобы не только map'ы, но и списки с векторами поддержать.
Не всё так просто. Сейчас эту техническую составляющую можно убрать — pop может возвращать std::optional. Но такая операция даёт разработчику (by design!) меньше гарантий, чем пара top/pop. Возникает, например, интересный вопрос: что делать, если во время создания копии объекта для возврата из pop возникло исключение? Что делать, если это случилось на этапе создания возвращаемого значения? Гарантировать, что pop всегда будет делаться через move нельзя — для объектов в стеке эта операция может быть попросту не определена. Полагаться на RVO/copy elision тоже не всегда можно.
В примере с in такая реализация сужает контракт. Работая с итераторами (казалось бы, удобно!) быстро утыкаешься в то, что для ассоциативных контейнеров и сиквенсов они разные. А стоит переключиться на значения — см. выше.
Спасибо, но я подожду С++20, там будет bool map::contains(key)
https://en.cppreference.com/w/cpp/container/map/contains, с поддержкой transparent comparator.
В Boost с версии 1.67 есть нечто подобное, без дополнительных макросов: https://www.boost.org/doc/libs/1_68_0/libs/hof/doc/html/include/boost/hof/infix.html
Спасибо, но я подожду С++20, там будет bool map::contains(key)
Метод std::map::count есть уже сейчас. Возвращает не bool, а int(0/1), но этого достаточно для любого практического применения. Впрочем, автора интересовало не только наличие элемента по ключу в контейнере, но и возможность (если он там есть) его редактировать без повторного поиска.
с поддержкой transparent comparator.
В перегрузке с transparent comparator собака зарыта. Например:
std::map<QString,Value> foo;
// ...
return foo.contains("bar");
Операторы сравнения QString и const char * определены так, что сначала из const char * создается QString, а потом сравниваются два QString'а. Итого N*log(N) лишних аллокаций, деаллокаций и преобразований кодировок в сравнении с:
return foo.contains(QString("bar"));
Вот и проблема, как убедить своих колег в том, что это натация стоит использования? Увидев такой код на ревью, меня первым делом спросят, где тесты? Нет тестов — значит оно точно работает (не)всегда так как я этого не ожидаю.
Где тесты? Где границы применимости т.е. при каких условиях такая натация это UB? Какие есть ограничения на контейнеры, на value_type, как я могу это использовать не по назначению (скорее всего случайно), и как запретить это на уровне компилятора?
Когда то я тоже увлекался подобными вещами, очень полезно и развивает, а потом в какой то момент я сказал себе, хватит заниматься борьбой с ветренными мельницами (с++). Никто и никогда в здравом уме не даст мне это использовать в продакшене. А самое главное это должно быть на уровне языка, а не библиотеки (даже если это и можно с эмулировать в виде библиотеки) потому, что ни одна утилита (статический анализотр, в том числе встроенные в компилятор ворнинги) про это счастье ничего не знает и не узнает. Значит только внимательное код ревью спасет меня и моих колег от проблем, но у меня и так полно мест за которыми мне нужно очень внимательно следить, я не хочу загружать в свой мозг еще и вот это все.
Еще раз, мне это очень нравиться как концепция и на уровне языка я всеми конечностями за такое, но это поделка на коленке почти наверняка не пригодна к применению в продакшене. Продакшен код нужно читать как очень плохой, несогласованный, полный багов, спагети код, который при малейшей возможности всегда проявляет UB. А еще мои (да и не только мои) колеги очень упрямые и даже если покрыть все 3х этажными тестами на все случаи жизни и предвидеть будующие измениния с++ (ну хотя бы лет на 5-6), то даже в этом случае у меня почти нет шансов убедить хотя бы парочку, что это того стоит.
К сожалению практической пользы в лучшем случае 0 (а для многих вообще отрицательная), но читать было приятно, пишите есчо, есть вероятность, что другие поделки будут полезней.
Консерватизм свойственен людям, тут ничего не поделаешь. Можно попробовать с каких-то более предсказуемых конструкций. Не сразу втаскивать оператор "in", а начать с чего-то меньшего. Но это, конечно, зависит от коллег, есть люди которые считают, что все что не предусмотрено языком — лишнее и не имеет право на существование.
P.S.
Мне больше повезло с коллегами сейчас. Для одной из задач, я использовал что-то типа именованных аргументов функции и перегруженный operator |
для объединения операций. Если интересно, то можно посмотреть на github.
Вот и проблема, как убедить своих колег в том, что это натация стоит использования?
Ну моя задача состояла в том чтобы, поделится с сообществом своими идеями. Убедить ваших коллег использовать это или нет, уже не моя забота. Я к идеям своих подчинённых, отношусь например без предубеждений.
Где границы применимости т.е. при каких условиях такая натация это UB?
Это всего лишь перегруженный оператор * UB у него такие же как и у любого другого перегруженного оператора *.
Какие есть ограничения на контейнеры, на value_type, как я могу это использовать не по назначению (скорее всего случайно), и как запретить это на уровне компилятора?
Это зависит уже от перегруженного оператора in, в принципе его перегрузить можно для чего угодно, я не вижу каких-то особенных препятствий.
Когда то я тоже увлекался подобными вещами, очень полезно и развивает, а потом в какой то момент я сказал себе, хватит заниматься борьбой с ветренными мельницами (с++). Никто и никогда в здравом уме не даст мне это использовать в продакшене.
Я бы не назвал, это борьбой с ветряными мельницами, я например делаю это для собственного развлечения в основном. Ну а если вам не разрешают использовать это в продакшене, то почему бы не использовать это в своём пэте, раз вам понравилось.
. А самое главное это должно быть на уровне языка, а не библиотеки
Я встречал довольно много библиотек которые активно используют макросы, за частую даже опасные.
К сожалению практической пользы в лучшем случае 0 (а для многих вообще отрицательная)
Сочувствую вам, сам когда-то работал в компании которая на С++11 то не хотела переходить, не то что там использовать подобные фичи.
но читать было приятно, пишите есчо
Спасибо, как раз намечается интересная статья.
Реализация оператора in в С++