лямбды в неоцененных контекстах (до C ++ 20)

В настоящее время я читаю статью P0315R1 который говорит о лямбдах в неоцененном контексте

В документе есть утверждение, которое объясняет почему лямбды не могут появляться в неоцененных контекстах (конечно, только до C ++ 20), как показано ниже:

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

Может кто-нибудь объяснить это утверждение на примере?


person cpp_enthusiast    schedule 17.04.2019    source источник
comment
@VTT Спасибо, посмотрю ссылку.   -  person cpp_enthusiast    schedule 17.04.2019
comment
Прочтите, что означает искажение имен, и подумайте, как это должно применяться к лямбдам, появляющимся в подписях.   -  person Passer By    schedule 17.04.2019
comment
@PasserBy Спасибо за ваш комментарий. Я уже знаю, что такое искажение имени. Однако я должен понять, как это применимо к лямбдам. Спасибо за хорошее предложение.   -  person cpp_enthusiast    schedule 17.04.2019


Ответы (2)


Небольшая предыстория: компоновщики не понимают перегрузки функций - они понимают только имена функций, как в языке C. Вот почему компиляторы C ++ искажают имена ваших функций. void foo(int) становится _Z3fooi. Искаженное имя кодирует типы всех аргументов. Если ваша функция возникла в результате создания экземпляра шаблона, все аргументы шаблона также будут закодированы. Если они сами являются шаблонными классами, их аргументы шаблона кодируются, и так далее рекурсивно, пока не будут достигнуты примитивные типы, такие как целые числа или указатели на функции.

Лямбда усложняют задачу, потому что каждая лямбда должна иметь отдельный тип. Это неплохо для лямбда-выражений, определенных в функциях:

auto foo() { return [](){}; }
auto bar() { return [](){}; }

foo и bar возвращают разные типы. Если вы затем передадите их в другой шаблон функции, имя этого шаблона будет искажено, как если бы было foo::__lambda1 или что-то в этом роде.

Если в decltype появятся лямбды, этот механизм нарушится.

void bar(decltype([](){}));
void bar(decltype([](){})) {}

Это прототип и определение? Или это две разные перегрузки bar? Как идентифицировать их в единицах перевода (как искажать имена)?

До сих пор C ++ запрещал даже задавать этот вопрос. Документ, на который вы ссылаетесь, дает ответ: такие вещи не могут иметь связи. Даже не пытайтесь их покалечить.

person Filipp    schedule 18.04.2019
comment
Ваши bar должны быть в классе, чтобы ODR объединял их (иначе они все равно могли бы быть отправлены с внутренней связью). - person Davis Herring; 18.04.2019
comment
Это действительно так? Они не статичны и не находятся в анонимном пространстве имен. - person Filipp; 18.04.2019
comment
Это верно на уровне реального инструмента (а не определения абстрактного языка): обратите внимание на .globl директивы. - person Davis Herring; 18.04.2019
comment
@Filipp Я думаю, мне нужно прочитать об этом немного больше. Можете ли вы указать мне на какую-либо документацию или ссылки по этому поводу? - person cpp_enthusiast; 20.04.2019
comment
Я не могу, извини. Попробуйте поиграть с лямбдами и шаблонами в проводнике компилятора Godbolt. Посмотрите на полные названия символов. Отключите разборку. Сделайте преднамеренные ошибки и посмотрите, что напечатает компилятор. - person Filipp; 22.04.2019
comment
@Filipp Я понимаю, что объявление decltype([](){}) в сигнатуре функции создает неоднозначность для компилятора, чтобы различать определение или перегрузки, но я не могу понять, как это влияет на искажение имени. void bar(decltype([](){})) создает уникальное искаженное имя, верно? Единственная проблема заключается в том, что компилятор не может вызвать эту функцию, потому что каждая лямбда имеет уникальный тип. - person cpp_enthusiast; 24.04.2019
comment
Основная цель изменения имени - дать возможность компоновщикам различать разные версии перегруженных функций. Если у нас есть две функции void bar(decltype([](){})){} и void bar(decltype([](){})){} (обе одинаковы), компилятор может различать их, потому что каждая лямбда создает другой тип, а затем компилятор может использовать эту уникальную информацию о типе для создания уникальных искаженных имен, конечно, мы не можем вызывать эти функции, к сожалению. Что мне здесь не хватает? - person cpp_enthusiast; 24.04.2019
comment
На самом деле компилятор не может создавать уникальные искаженные имена для bar. Что, если другая единица перевода также объявляет void bar(decltype([](){}){} в том же пространстве имен? У них одна и та же функция? Вот почему я думаю, что это было запрещено до C ++ 20. Указание компилятору не искажать лямбды позволяет нам писать auto baz(auto x, auto y) -> decltype([x, y]() { /* complicated logic */ return result; }()); - person Filipp; 25.04.2019
comment
@Filipp Я думаю, что компилятор компилирует только одну единицу перевода за раз и не знает и не заботится о других единицах перевода. Так что в идеале он создает уникальное искаженное имя. Но проблема возникает, когда компоновщик пытается объединить несколько определений одной и той же сущности (например, несколько определений одной и той же встроенной функции в файле заголовка) на основе этого искаженного имени. поправьте меня, если я неправильно понимаю. - person cpp_enthusiast; 25.04.2019
comment
Ты прав. Компилятор даже в принципе не может сделать имя уникальным для всей программы. - person Filipp; 25.04.2019

Если у нас есть встроенная функция с decltype of lambda в сигнатуре функции в файле заголовка. Компилятор генерирует уникальное искаженное имя для этой функции в каждой единице трансляции, в которую она включена.

Таким образом, в конце компоновщик не может объединить эти несколько определений «одной и той же» функции, потому что искаженные имена различаются.

person cpp_enthusiast    schedule 25.04.2019