C++ Embedded
Долой ()!
Мало кто знает, но я распечатываю каждый опубликованный черновик стандарта. Читать их, конечно, не успеваю, но заботливо кладу на полочку. Вдруг интернет совсем заблокируют, электричество закончится или еще какая нужда приключится. Вот, растапливая печку, я листал старенький n3242 за 2011 год и увидел интересную деталь в разделе 5.1.2 Lambda expressions [expr.prim.lambda]. Речь, конечно, про объявление лямбда-выражения:
lambda-introducer lambda-declarator[opt] compound-statementПервая часть, lambda-introducer, описывает захват лямбды, последняя - непосредственно блок кода функции.
Средняя часть определяет сигнатуру функции:
( parameter-declaration-clause ) mutable[opt]exception-specification[opt] attribute-specifier-seq[opt] trailing-return-type[opt]Когда-то я не обратил на это внимания, но сейчас, после обнародования всех тех ужасов, что GCC вытворяет с лямбдами, не мог пройти мимо.
Несчастное ключевое слово mutable приковано к списку параметров! И наверняка против его воли.
Это значит, что лямбде разрешается быть такой: []{}, либо такой: []() mutable {}, но она не может быть [] mutable {}.
Оказывается, не по стандарту это, не по понятиям.
Так и пропадало бы это невинное и понятное, в общем-то, выражение с печатью изгоя на сигнатуре, но в июне 2018 года некто Alex Christensen из Apple и JF Bastien оттуда же (хотя потом убежит в Тойоту, но не осуждаем) написали предложение "Down with ()!"
Мне нравится этот документ за самую краткую и четкую аннотацию, какую я видел:
Предложение по удалению ненужных () из лямбд.Ни капли воды! Далее так же сухо подавалась суть.
В настоящее время для лямбда-выражений без параметров не обязательно писать круглые скобки. В спецификации [expr.prim.lambda] (раздел 8.4.5, пункт 4) по этому поводу сказано следующее:
Если лямбда-выражение не включает в себя лямбда-декларатор (lambda-declarator), оно интерпретируется так, как если бы лямбда-декларатор был пуст: ().Это позволяет опустить неиспользуемые () в простых лямбдах.
𝚊𝚞𝚝𝚘 𝚠𝚒𝚝𝚑𝙿𝚊𝚛𝚎𝚗 = [𝚜𝟷 = 𝚜𝚝𝚍::𝚖𝚘𝚟𝚎(𝚜𝟷)] () {};
𝚊𝚞𝚝𝚘 𝚗𝚘𝚂𝚎𝚊𝚗 = [𝚜𝟸 = 𝚜𝚝𝚍::𝚖𝚘𝚟𝚎(𝚜𝟸)] { /* Так можно */ };
Конкретно эти лямбды владеют строками, но не могут их изменить, s1 и s2 объявлены как const (т.к. лямбда по умолчанию const), для этого нам нужно ключевое слово mutable.
𝚊𝚞𝚝𝚘 𝚠𝚒𝚝𝚑𝙿𝚊𝚛𝚎𝚗 = [𝚜𝟷 = 𝚜𝚝𝚍::𝚖𝚘𝚟𝚎(𝚜𝟷)] () 𝚖𝚞𝚝𝚊𝚋𝚕𝚎 { 𝚜𝟷 += "𝚍"; };
𝚊𝚞𝚝𝚘 𝚗𝚘𝚂𝚎𝚊𝚗 = [𝚜𝟸 = 𝚜𝚝𝚍::𝚖𝚘𝚟𝚎(𝚜𝟸)] 𝚖𝚞𝚝𝚊𝚋𝚕𝚎 { /* Ошибка! */ 𝚜𝟸 += "𝚍"; };
Как ни странно, текущий стандарт требует наличия пустых скобок при применении mutable ключевого слова. Это правило контринтуитивно, приводит к распространенным синтаксическим ошибкам и загромождает код. При компиляции clang мы даже получаем синтаксическую ошибку, указывающую на то, что компилятор точно знает, что происходит:
example.cpp:11:54: error: lambda requires '()' before 'mutable'Собственно, GCC 10 реагировал схожим образом, а уже GCC 11 стал заметно мягче относиться к нарушению стандарта:
warning: parameter declaration before lambda declaration specifiers only optional with '-std=c++23' or '-std=gnu++23' [-Wc++23-extensions]"А чего такого? - удивленно таращится на тебя компилятор, - в c++23 исправили же!"
Действительно, ребята добились своего, mutable оторвали от списка параметров. Однако в с++20 это все еще грех!