Pull to refresh

Comments 112

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

Попробуй новые языки. Rust, например, предлагает безопасность намного выше, чем C++, не имеет тонны Легаси, а также не фанатичны к выборы парадигмы. D сейчас есть в варианте без сборщика мусора. А если нужно что-то простое, так ещё и "поближе" к железу, то есть старый добрый C с расширениями GCC. Там и автовывод типов добавили, и безопасные макросы, и дженерики, и даже RAII.

Так как раз после Rust'а как раз C++ и воспринимается грустнее: много похожих на совеременный C++ идей, нет legacy, выглядит аккуратнее и без килотонн подводных камней.

А D язык хороший, но «не взлетел», к сожалению.
UFO just landed and posted this here

Alexey_Alive, направьте меня в сторону автовывода типов? Не нашел среди C Extensions в документации GCC. RAII — это вы об атрибуте __cleanup__?

Да, я про cleanup. Это некий аналог RAII, ибо в Си нет классов. По поводу вывода типов, в Gcc есть аналоги auto и decltype из cpp: __auto_type и typeof. Благодаря им можно писать безопасные макросы, например (в GCC в ({ }) последнее выражение возвращается.)


#define max(a,b) \
  ({ __auto_type _a = (a); \
      __auto_type _b = (b); \
    _a > _b ? _a : _b; }) <source>

Спасибо. Про __cleanup__ знал, про __auto_type — нет

Ну не знаю, у меня нет какого-то ощущения «борьбы с языком». Появляются новые фичи, которые ты можешь использовать (предварительно разобравшись, как они работают), а можешь не использовать, это ж никто не заставляет.

Поддерживаю. Но самое трудное — согласовать свои желания с коллегами. Чтобы не получилось ситуации, что один использует с++03, а другой фигачит лямбды с вариадиками.

Ну, для этого я и знакомлюсь с новыми стандартами, чтобы худо-бедно разбираться в «лямбдах с вариадиками» :) Так-то в принципе я и сам использую те же лямбды, constexpr, кортежи, считаю это удобными механизмами. Другое дело что всякие SFINAE кунштюки например я использую редко, это скорее для разработчиков библиотек, они, так сказать, страдают за нас, чтобы нам было проще и естественней использовать их библиотечные интерфейсы :)
Вот да, надо просто пользоваться языком, где всё явно, мало слов, нет особой борьбы с языком и можно кратко и выразительно оформить нужные алгоритмы в код, компилирующийся в быстрый исполняемый файл. Как там его, язык этот, не напомните название?
У Эллочки Людоедки был такой. Только названия у него нет — сэкономила одно слово в своем словаре.
Ну тогда Lisp, как Дядька Боб глаголит
UFO just landed and posted this here
UFO just landed and posted this here
разбить программу на два слоя: оптимизированный на с++, и логику на шарпе
UFO just landed and posted this here
UFO just landed and posted this here
UFO just landed and posted this here
А? Классика же:

class singleton
{
public:
    static singleton &instance()
    {
        static singleton inst;

        return inst;
   }
private:
    singleton() {}

    singleton(const singleton &) = delete;
    singleton(singleton &&) noexcept = delete;

    singleton &operator=(const singleton &) = delete;
    singleton &operator=(singleton &&) noexcept = delete;
};


Что касается «с удалением — труба», так это тоже просто от плохого знания предмета:

Destructors (12.4) for initialized objects of static storage duration (declared at block scope or at namespace scope) are called as a result of returning from main and as a result of calling exit (18.3). These objects are destroyed in the reverse order of the completion of their constructor or of the completion of their dynamic initialization. If an object is initialized statically, the object is destroyed in the same order as if the object was dynamically initialized. For an object of array or class type, all subobjects of that object are destroyed before any local object with static storage duration initialized during the construction of the sub- objects is destroyed.

Я всегда замечал, что наиболее ярые критики C++, которые «ни за что и никогда», просто плохо знакомы с языком.
UFO just landed and posted this here

Ссылочку ниже прочли? :) Там и рецептик приводится. Понятно, что чуть сложнее, чем мой пример выше, и его тоже можно при очень сильном желании поломать, но тем не менее.

Конечно ярык критики С++ это те кто его не осилил.
Так в этом и состоит суть критики: сложно осилить.
Я каждый день пишу на С++ и только на С++.
И я его сегодня знаю хуже чем лет пять назад.
Работа комитета вызывает больше негатива, чем позитива.
Да, язык нужно развивать. Но комитет ударился в впихивание всего подряд. Половину нового можно выкинуть, потому что оно является синтаксическим сахаром, мало нужным в повседневной работе. По сути просто пытаются один язык превратить в другой. На выходе получается гребаный франкенштейн. Не удивлюсь если через пару лет в стандарте внезапно появится GC. Не, ну а чо, полезная же штука!

Ну, я не согласен. Например, spaceship operator — это синтаксический сахар? Да. Нужно ли выкинуть? ИМХО нет, потому что он избавляет от написания просто тонны boilerplate кода. Да, конечно, можно писать "по старинке", педалить все вот эти operator ==, !=, <, >, а ещё про friend operator не забыть, все по новой… Но зачем? В чем профит? А "ниасилить" тоже можно по-разному, можно не разбираться в тонкостях SFINAE, а можно быть неспособным реализовать синглтон без утечки памяти или испытывать сакральную боязнь object slicing'а, потому что где-то прочёл, что это "плохо", а почему именно это плохо и в каких случаях — не понимаешь, а ведь эти вещи ещё из C++98. В общем, незнание незнанию рознь.

Я не говорю, что всё что делает комитет хрень.
Но вот тот же структурный биндинг из статьи — нафиг не нужен. Взять из переменной набор, вместо того чтобы обращаться к полям через переменную, что не составляет труда. К тому же тот же with из delphi гораздо более адекватное и красивое решение, если уж настолько критично не писать название переменной для доступа к члену…

Вообще, ИМХО, комитету не хватает некой дополнительной «проверки временем». То есть добавили фичу, если через 5 лет этой фичей не пользуются большинство крупных игроков на С++ рынке — она выносится еще раз на обсуждение и если весомых доводов её оставить нет — depricated и досвидания в следующей редакции.

Ну это я так понимаю скорее для всяких кортежей, особенно если в них ссылки. Вместо написания простынки из создания временных tuple и std::get.

deprecated и досвидания в следующей редакции
Верный путь убить язык, потому что если какой-то код будет компиляться с /std: с++14, но не /std:c++17 — будет расти фрагментация.
Значит более жестко подходить на этапе перехода от экспериментальной ветки в стабильную.
Чтобы не было ситуаций, когда «Так, сегодня мы утверждаем переход в релиз фичи Х. Кстати, кто уже делал проекты с её использованием?.. Кто хотя бы пробовал?.. Ясно, переносим обсуждение на следующую встречу».
Напомню, примерно так было с одной из фич на последней встрече.

Засрать язык фичами, которые использует два с половиной фаната — это тоже верный способ убить язык.
Тут я согласен: новое нужно вводить с большой осторожностью. Но отзывать уже принятое — нельзя.

Нам, как пользователям стандарта, какое должно быть дело до обсуждаемого. Приняли — придётся с этим жить. Пока не приняли — даже и смотреть незачем, когда оно ещё будет принято…
То есть добавили фичу, если через 5 лет этой фичей не пользуются большинство крупных игроков на С++ рынке — она выносится еще раз на обсуждение и если весомых доводов её оставить нет — depricated и досвидания в следующей редакции.

ну примерно так и происходит, только не 5 лет, а 2-3 года. Или вы никогда не пытались пользоваться бустом/experimental?

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

какие фичи с++17, по-вашему, используют «два с половиной фаната»? С++14? С++20?
Что там насчет «Garbage collector support»? :))
А если серьезно вопрос достаточно сложный. Я на него не могу твердо ответить.
На любой мой ответ можно будет возразить: «Вот код, где это используется». Статистики то у меня нет, только ощущение от работы в разных командах плюсовиков.
Что там насчет «Garbage collector support»? :))

Это с++11, там да, есть несколько редко используемых фич. Собственно, после него в методологии развития стандарта многое поменялось.

На любой мой ответ можно будет возразить: «Вот код, где это используется».

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

Какое мнение? Я нигде не делал утверждение, что сейчас в стандарте есть фичи которые используется два калеки. Делать такое утверждение — большая ответственность. Надо быть большим экспертом. принимающим активное участие в анализе кодовой базы чтобы такое утверждение сделать.
Я сказал что делать такие фичи — ошибка. Обозначил тенденцию.
UFO just landed and posted this here
Но, как и написано в статье, понимание того, что при этом будет создано, требует знаний и усилий. Не скажу, что критично, но осадочек остается: например, не будет ли значимых различий между компиляторами? Между версиями одного и того же компилятора?
UFO just landed and posted this here
Нет, стандарт же вполне однозначно всё определяет.

О да, стандарт сила, кто бы возражал. Вспомните, к примеру, так прекрасно разобранную О'Двайром историю про gcc, clang, msvc и разные форматы декорирования имен.
Что касается структурного связывания, то у меня не хватает ума, чтобы без просмотра сгенерированного ассемблера наверняка понять, что и как создается.
Не удивлюсь если через пару лет в стандарте внезапно появится GC. Не, ну а чо, полезная же штука!

у меня для вас новости из 11-ого года
Счетчики ссылок(чем являются смартпоинтеры) и GC — сильно разные вещи.
Смартпоинтеры — логичное развитие голых указателей. По сути голые указатели не должны вообще хранится где-либо, кроме участка кода, который с ними непосредственно сейчас работает. Тут ни оверхеда по производительности нет, ни непредсказуемости.
А вот GC — совсем другая история. Но, повторюсь, все таки верю в светлое и надеюсь до имплементации GC в стандарте дело не дойдет.
я не про смартпоинтеры. По ссылке выше есть набор функций из категории «Garbage collector support», и он присутствует в стандарте языка с++. Другое дело что стандарт даже не требует от компилятора поддержки этого функционала чтобы считаться полностью соответствующим стандарту, разрабы компиляторов этот функционал не делают, а народу попросту пофиг.
Это сделано для того, чтобы те, кому нужен GC могли его относительно легко реализовать.
UFO just landed and posted this here
В языке даже синглтон толком нельзя реализавать без утечки памяти.

Разверните эту свою мысль. А то есть мнение, что вы до C++ так и не дошли, так сказать, перед тем, как от него уходить.
Ну не знаю, там речь все-таки не об утечке (деструктор-то у синглтона вызывается исправно, хехе), а о неопределенном поведении, вызванном конкретными «диверсионными» действиями со стороны пользователя библиотеки (понятно, что по незнанию). Случай интересный, но к «утечкам памяти» от синглтонов как таковых он все-таки отношения не имеет.
Не совсем исправно. В SingletonShared, пример 3 рассмотрен вариант, когда деструктор синглтона как раз не вызывается. Правда, это скорее не про утечку памяти, а про сбой требуемой деинициализации типа подчистки временных файлов.

Синглтон по определению не может вызвать утечку памяти. Просто потому что для утечки требуется бесконтрольное выделение памяти под всё новые объекты, а синглтон всегда один.

Видимо имеется в виду, что «если я создам синглтон через new, то некому будет вызвать delete». Что само по себе правильно, конечно, но синглтоны создаются не так.
UFO just landed and posted this here

Можно, если создавать из статического метода этого же класса.

UFO just landed and posted this here
Бывало в юные года…
Писал на «сплюсплюс» тогда…
Теперь угас уж жар в крови:
На «Си» пишу, на «чистом С»…

P.S.
Статья хорошая, понравилась.
Вот и я о том же…
Знаю С, но как понять синтаксис нового C++ для меня загадка.
Си любят за то, что там таких подводных камней нет (есть другие наверное), к примеру — там всегда есть явное выделение памяти и удаление. То есть известно всегда какие инструкции будут выполнены, а не предположения о том какой конструктор будет вызван или не вызван, причём поведение это в С++ меняется от версии к версии (copy ellision).

Вот статья которая очень хорошо описывает подобную ситуацию с С++ (инициализация)
habr.com/ru/post/438492

У меня складывается ощущение, что из C++ пытаются сделать современный язык путем натягивания совы на глобус. Где нужна скорость, использую чистый C. Где надо быстро что-то сделать, использую C#. Кто для каких целей использует C++ с последними плюшками?
C++ всё ещё удобнее в большинстве случаев. В С слишком много нужно делать руками.
Мне кажется, что вот эти трюки с явным-неявным управлением памятью требуют достаточно много когнитивных ресурсов. Когда пишешь на С — сосредоточен и внимателен ко всему, когда пишешь на С# — забиваешь на все эти детали. А С++ вроде сам за тебя много делает, но расслабляться нельзя.
Сосредоточен и внимателен только до тех пор, пока код не становится достаточно большим и сложным, чтобы не помещаться целиком в когнитивные ресурсы...)
Перефразируя известное высказывание
Программисты жалуются, что у них сложный код? Пусть простой пишут.
Сам по себе код может быть простым, однако всё усложняется, когда проект становится большим: код пишет сразу целая команда, связи между модулями, библиотеками, опять же легаси (которое или уже было или появляется со временем)…

Да даже свой собственный код через некоторое время становится сложным для понимания/вспоминания.

Так что я предпочитаю использовать инструменты, которые как-то автоматизируют процесс, руками, конечно, хорошо, но, через некоторое время, это становится слишком «дорого».
Это общая проблема для всех языков: есть границы масштабирования при росте размера и сложности. В каждом языке есть свои средства для выражения абстракций, структурирования кода и т.д., но граница, за которой их начинает не хватать все равно есть. У C++, имхо, эта граница дальше, чем у C, но не сказать, что уж сильно дальше.
Решить проблему можно только «внеязыковыми» средствами: архитектурой, продуманным дизайном и т.д. И вот тут есть интересный момент: можно добавлять в язык выразительные средства, а можно «опрощать» язык, чтобы уменьшать, так сказать, удельное логическое сопротивление на строчку кода. Получается, что кода-то очень много, но он «простой», в нем даже IDE может разобраться и построить схемы, навигацию и т.д. C — он изначально достаточно прост. А, к примеру, Java и Go сознательно сделаны такими.
В C++ просто надо явно объяснять компилятору то, чего ты хочешь получить на выходе. Если понимать этот «метаязык» то проблем вообще никаких. Причем там же буквально несколько простых принципов, не сказать даже что что-то сложное. Особенно ярко это видно если сравнивать современный C++ с тем что было до него. Там где раньше было «правило трех / правило пяти» сейчас работает «правило нуля» — компилятор при правильном его использовании все сам соберет верно, ничего самостоятельно переопределять вручную не нужно.
У вас же есть CI? Посмотрите там в логах, сколько раз за последнюю неделю юнит-тесты плюсового кода падали с сегфолтом, а сколько раз по ассерту. Потом расскажете, какой компилятор умный, и как он не даёт ошибаться.

Шутите? Это редчайшие события, как в тестах, так и в проде. В тестах хорошо если раз в год бывает сегфолт, в проде машин больше — там несколько случаев на сто тысяч запусков, но это в основном сбои железа, прежде всего памяти, они четко по машинам привязаны и при замене железа исчезают. Ассерты на логику да, могут вылетать но это с языком уже не связано.

UFO just landed and posted this here
Нет, это понятно что в мастере у вас все хорошо а на проде вообще идеально, вы посмотрите в каком состоянии код у разработчиков до того как они отправят его на ревью.

У нас тут распределённая сборка есть, так что можно посмотреть кто и что компилирует. На глаз, примерно 80% попыток вообще не компилируются. Я не поленился, и просмотрел последние 50 фейлов, когда проект собрался, юниты запустились но не прошли:
В 41 случае сработал ассерт в тесте
Ещё четыре падения когда сработал ассерт в продуктовом коде: попытались засунуть null в словарь, что-то не вызвали, и т.п.
Ну и пять сегфолтов родимых.
Ну, эти 80% не компилирующегося кода — это как раз то что «компилятор не пропустил» :). Это можно назвать недостатком языка, а можно — достоинством (скажем python который я нежно люблю в той же ситуации запустится, но затем помрет в рантайме что хуже). Падающие тесты — тоже хорошо. Нередко это говорит о том что они отлавливают достаточно много нетривиальных ситуаций, поэтому их и не получается пройти «с первой попытки» (а то видел я тесты которые внешне выглядели похоже на правду но на деле не фейлились даже на ошибочном коде, хех :)). Не думаю кстати что с тестами ситуация будет отличаться в других языках.

Но вот индексная арифметика — да, до коммита в прод может сегфолтить или ассертится, согласен. Разыменование null тоже бывает. Но это в любом языке будет ассертится, это ошибки в логике и не очень понятно при чем тут плюсы. А конкретно специфичную для плюсов работу с памятью в C++11 до сегфолта довести очень сложно.
Не анализировал, почему у них не собирается. Может в скобках запутались, или хедеров не хватило. А может быть действительно компилятор что-то обнаружил.

Абсолютно согласен, что юниты хорошая штука для любого языка, они помогают не держать в голове одновременно все требования. А если тесты хорошие, то даже можно обо всех требованиях и не знать — тесты подскажут что апи добавления комментов должно проверить не забанен ли пользователь, а алгоритм поиска пути обходит скалы и реки, и предпочитает дороги. Ну или что там продукт делает.

А конкретно специфичную для плюсов работу с памятью в C++11 до сегфолта довести очень сложно.

Я знаю имена как минимум пятерых, кто сегодня смог это сделать. Не думаю, что они к этому как-то специально стремились. Просто чуть ослабла концентрация, отвлёкся на задачу и привет.
Я знаю имена как минимум пятерых, кто сегодня смог это сделать.

Тогда Вы явно делаете что-то неправильно :)
То есть, существуют некие очень простые правила, которые можно вставить, например, в плагин к IDE, и они бы подсветили этим товарищам место где они делают что-то явно неправильное и вот эти микро-факапы бы не случились?
Умные указатели + грамотно спроектированное приложение. К сожалению не скажу что это тривиально автоматизируется, но научиться вполне можно :)
Получается, что компилятор С++ не может проверить корректность полностью, а значит за ним придётся доделывать нейронам в чьей-то голове. Автоматизация же, ну.
Корректность полностью проверить не может ни один язык кроме, возможно, функциональных :)
Ну простые вещи уже много где научились проверять, не без влияния функциональщиков конечно, но что поделаешь. Чтобы получить в 2019 году NullReferenceException, или тем более IndexOutOfRangeException надо специальный комментарий рядом написать, иначе кодоанализатор загнобит.
Особенно нельзя расслабляться, если в используемых тобой библиотеках управляют памятью вручную. :)
Какие современные языки умеют RVO, например? Мне кажется, что философия С++ достаточно самобытна, чтоб говорить не о натягивании совы на глобус, а просто о планомерном развитии. C++11 пофиксил многие проблемы тяжелого наследия С (привет, ручное управление памятью), теперь просто добавляют всякий сахар/оптимизации, как и другие современные языки.
Добавляют новые способы реализовать то, что и так уже можно нормально(без извращений) реализовать. Это усложнение языка и ведет только к плохому.
например, что такого добавили, что можно было без извращений реализовывать раньше?
Знаете что такое boost?
Он весь реализован на базовом С++.
И он почти весь переехал в стандарт.
Кстати, далеко не самое плохое что внесли в стандарт.
А вы заглядывали внутрь boost? Боюсь, если разобраться, как там внутри всё работает, то фильмы ужасов или самое извращенное порно могут начать казаться уже и не такими страшными…
UFO just landed and posted this here
boost
Вы же обещали без извращений! Внутри boost к ним как раз таки и приходится прибегать. Что бы этого избежать, отчасти и добавляют новые фичи в стандарт.
Он весь реализован на базовом С++.
с кучей макросни и такого когда, на который смотреть больно.

Так что вопрос открыт: что такого добавили, что можно было без извращений реализовывать раньше?
rust должен уметь — бекенд там llvm-ный, а RVO (насколько я в курсе) корректен для любого типа. Разве что если фронтенд такую оптимизацию прокидывать не умеет
С++ остается идеальным языком для CAD-систем, к примеру. Или игр. Вообще для любых приложений сколь-либо крупного размера где важно быстродействие. И он очень удобен если его правильно уметь использовать. Емкий, выразительный и быстрый код. Взять тот же Eigen к примеру. Он очень C++-style, в любом другом языке по-моему его аналоги просто невозможны. Он дает понятный читаемый код. И при этом он (в моих тестах) весьма заметно опережал «c-style» MKL, который был еще и менее читаемым. Хотя вот казалось бы.

Я резко не соглашусь с тем что C++ не является «современным языком». C++11 был радикальным шагом вперед, язык стал намного удобнее в использовании, более читабельным, более производительным. Ушла необходимость бороться с языком во многих местах. И сейчас C++17 — тоже большой шаг вперед. Не такой драматичный как C++11, но очень заметно упрощающий жизнь и убирающий потребность в некоторых критичные велосипедах.
В примерах начиная с
Base foo3(bool c)
{
    Derived a,b;	
    if (c) {
        return std::move(a);
    }
    return std::move(b);
}
(если здесь предполагается что Derived это производный класс от Base) происходит object slicing. Заботиться при этом о реализации move semantics как-то уже излишне.
В самом по себе object slicing нет ничего «незаконного», это просто механизм, у которого есть вполне легитимные применения. Почему бы и не озаботиться move semantics, если нужно?
Как уже правильно заметили — это вполне себе рабочая практика. К тому же move — это, по сути, просто способ передать владение каким либо ресурсом без особых накладных расходов, и таким ресурсом как раз может быть что-нибудь общее, что как-то особенно считается в наследнике, например.
По-моему Вы описываете очень экзотическую реализацию того что проще и лучше реализовывать паттерном Factory
Фабрики бывают разными. Не скажу, что slicing — это хороший метод для реализаций чего-нибудь, но это используемый метод. Один из примеров вполне используемого кода (некоторая обёртка над сырыми указателями) есть в презентации из статьи (CppCon 2018: Arthur O'Dwyer “Return Value Optimization: Harder Than It Looks”)
Просто срезка режет глаз. Но в контексте темы copy elision это наверно, да, может служить иллюстрацией, что вот так вот, можно вызвать move-конструктор от некоторой части объекта.
C++ новейший настолько сильно отличается от C и «C с классами», что просто дрожь берёт.
Есть хоть какая-нибудь литература, чтобы попытаться сделать шаг через пропасть?

Да, есть. Погуглите "C++ Core Guidelines".

Спасибо!
Или упаду в пропасть или пойму.
Вариантов два)
Мне нравится как не с++ программисты обсуждают с++)
Вся вакханалия началась с STL, потом появился boost и прочее.
То есть по сути библиотеки и шаблоны создали новый язык программирования.
В итоге код на современном C++ ну явно нечеловеческий.
Он нечитаем.
100500 минусов в мою несуществующую карму, но это так.
Это всё появилось не от хорошей жизни, без него было ещё хуже.
Что значит «не от хорошей жизни»?
Вот прекрасная статья от «отцов основателей», почему STL именно такая и какие задачи она решала.
habr.com/ru/post/166849

C++ успешен, т.к., вместо попытки предложить машинную модель, изобретенную разве что в процессе созерцания своего пупа, Бьярн начал с C и попытался развивать C далее, предоставляя больше техник обобщённого программирования, но в контексте рамок этой машинной модели. Машинная модель C очень проста. У вас есть память, где находятся сущности. У вас есть указатели на последовательные элементы памяти. Это очень просто для понимания. C++ сохраняет данную модель, но делает сущности, располагающиеся в памяти, более исчерпывающими, чем в машине C, т.к. C имеет ограниченный набор типов данных. А именно, C имеет структуры, предоставляющие разновидность расширяемой системы типов, но он не позволяет вам определять операции над структурами. Это ограничивает расширяемость системы типов. C++ существенно продвинул машинную модель C к действительно расширяемой системе типов.
С++ уверенно движется по пути когда-то намеченным перлом. Если всё так продолжится то известную картинку можно будет переделывать под C++.

image
Пора уже, мне кажется, разделять С++ на «С++ для разработчиков библиотек и компиляторов» и «С++ для обычных программистов». Я читаю все эти нюансы нюансов новых опциональных фич завтрашних стандартов — и офигеваю. Ума не приложу зачем мне это всё может понадобится. Я использую в ежедневном программировании крайне простое подмножество С++: классы, STL, ну там редкий шаблон пробежит раз в полгода. Я подозреваю, что если бы я писал, например, код какой-то библиотеки Boost, то мне понадобилось бы значительно больше инструментов. Но мне не нужно, а их всё пихают и пихают в язык.
Вас разве кто-то заставляет все это использовать? :) А так например для меня из перечисленного как минимум structured binding выглядит полезным, так как я широко использую std::tuple и иже с ним. В «обычных программах», не в библиотеках.
Пока я пишу сам свой собственный проект, меня никто не заставит ничего использовать. Но когда над проектом работает команда (что, согласитесь, в среднем бывает значительно чаще), начинаются разночтения в предпочтениях. Вот тут то обширность инструментов и сыграет свою роль в формировании конфликтов мнений.
Это обычно как-то проводится через нормативные документы. Скажем, в гайдлайнах написано, что все исходные тексты должны гарантированно компилироваться с -std=c++98 (и соответственно настроен build server) и все — все пишут на «C с классами», без всех этих вот новомодных инструментов. А если в гайдлайнах написано, что использовать новые инструменты можно и нужно — ну да, придется использовать. Это скорее политический вопрос, чем технический.
Ну какой программист в своём уме перед командой заявит, что он не осиливает, поэтому давайте останемся на c++98 )))
Хмм. А разве
return std::move(b);

не является бесполезным/вредным? Скотт Майерс в «Эффективный и современный С++. 42 рекомендации по использованию C++11 и C++14» писал, что в лучшем случае компилятор поймёт нас, хотя не обязан, а в худшем мы сломаем RVO/NRVO. Или в 17 стандарте что-то с тех пор кардинально поменялось?
Тут всё несколько сложнее:
компилятор, вероятно, попытается сделать всё, чтобы выбрать более оптимальный вариант вернуть значение, однако, если RVO/NRVO нельзя сделать или нельзя сделать неявный move, то значение будет копироваться (lvalue).

RVO/NRVO сломать очень легко, с неявным move есть как минимум defect report (CWG1579), который его запрещает, если тип функции и тип возвращаемого значения разные (даже если есть возможность сделать move), поэтому в некоторых случаях нужно явно писать move.

Можете посмотреть видео с cppcon, где есть чуть больше примеров: CppCon 2018: Arthur O'Dwyer “Return Value Optimization: Harder Than It Looks”

И поищите подробнее про диагностку у clang, например: -Wreturn-std-move

Рекомендации — это хороший вариант делать всё относительно хорошо в среднем (как минимум код будет работать), но бывает и так, что можно сделать более производительно/правильно, если разобраться, что за всем этим стоит на самом деле.
Сначала претензия к коду, потом вопрос. Вот это:
template<>
std::tuple_element_t<0, Foo> const& Foo::get<0>() const
{
    return _bar;
}

ничем не отличается от этого:
template<>
std::tuple_element_t<0, Foo> & Foo::get<0>()
{
    return _bar;
}

О таком clang даже из коробки с -Wall сообщает:
warning: 'const' qualifier on reference type 'std::tuple_element_t<0, Foo>' (aka 'int &') has no effect [-Wignored-qualifiers].

Теперь к делу. Structured binding имеет вполне понятные подводные камни со ссылками, и вы итак об этом должны были знать, потому что cppreference читали перед написанием статьи. Простой пример, надо обратить внимание на '&':
#include <tuple>

std::tuple<int, float> foo();

int main()
{
  auto [a, b] = foo();
}
превращается в:
#include <tuple>

std::tuple<int, float> foo();

int main()
{
  std::tuple<int, float> __foo7 = foo();
  std::tuple_element<0, std::tuple<int, float> >::type& a = std::get<0UL>(__foo7);
  std::tuple_element<0, std::tuple<float> >::type& b = std::get<1UL>(__foo7);
}


Вопрос: зачем при связывании подсовывать другой тип? Я не вижу ни одного нормального юзкейса, где бы это пригодилось — очень похоже на антипаттерн. Канонический пример, где был бы нужен SB вот такой:
if (auto [ iter, success ] = my_set.insert("Hello"); success) do_something_with(iter);


А теперь посмотрите только на свой пример:
Foo foo;
const auto& [f1] = foo;

Каким образом я, как читатель кода, пойму какой тип имеет f1 (опустим вопрос использования SB для одной переменной), если я не видел метафункций для Foo? Почему я должен предполагать, что передо мной ссылка на инт?
Кажется примерно это и разбирается в примере?
В том плане, что привычная с виду const функция класса может вернуть не-const внутренний объект. И дальше приводится пример, как это поправить.

Как читатель кода поймет, что перед ним за тип? Достаточно просто, если читатель знает, что такое SB и помнит, что у него нет реализации по умолчанию для пользовательских типов, поэтому надо посмотреть в код/документацию по этому поводу.

Понятно, что со стандартными типами (tuple, pair, array), не надо подменять типы, но с пользовательскими отсутствие const там, где он по идее должен быть, может оказаться весьма неприятным.

P.S.
Для примера замените int& на пустой какой-нибудь класс Bar&, а потом передайте const Foo& в функцию, возьмите от него SB и проверьте, будет ли результат константной ссылкой.
Про «привычную с виду» const-функцию: надо бы тогда было отдельный параграф завести, но вообще это не проблема 17-х плюсов, она старее. Лично я эту часть статьи прочел как ненужное отступление от темы.

Если все-таки темой статьи было скрытое обсуждение type deduction в разрезе SB, то тогда стоило так и назвать ее, меня бы тогда ничего не смутило. :) Потому что просто SB — то, на что я дал ссылку выше. Задуманное использование максимально простое: там, где раньше надо было бы использовать std::tie, теперь можно наставить квадратных скобок.

А с предложенным кодом у меня есть два варианта прочтения:
1) Если действительно надо связывать одну переменную с другой вот таким способом… это ужасное нарушение инварианта класса. Потому что int& _bar из примера — приватное поле Foo, его действительно стоит таким образом выставлять наружу? Успокойте что это не так. Это даже закрывая глаза на нецелевое использование инструмента, которым планировалось легко возвращать несколько переменных из одной функции.

2) Наверное, связывание будет минимум на 2-3 переменные, и что тогда, чтобы прочесть такой код и быть уверенным, что всё в порядке, мне надо пойти в определения этих 2-3 типов и посмотреть где же у них там std::tuple_element_t<i, Foo> мелькает? И потом держать это в голове? Наивно предполагать, что так хоть кто-то будет делать больше одного раза. Это очень хрупкий код, который теряет в самодокументируемости практически сразу.

По мне так и то, и то — повышает хрупкость кода без видимых плюсов. В будущем, когда больше людей так будут делать, это должно стать антипаттерном.
Sign up to leave a comment.