Подтвердить что ты не робот

Что, если выражение лямбда С++ 11 поддерживает аргументы по умолчанию?

Я думаю, что следующий код очень удобен и не вреден:

auto fn = [](bool b = false) -> int // NOT legal in C++11
{
    return b ? 1 : 0;
}; 

Почему С++ 11 явно запрещает аргументы по умолчанию для выражения лямбда?

Я просто задаюсь соображениями и соображениями.

Я хочу знать "ПОЧЕМУ", а не "ЧТО" говорит стандарт С++ 11.

4b9b3361

Ответ 1

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

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

  • Вы можете вызвать лямбду через std::function. Параметры по умолчанию не будут работать без изменения системы типов.

Я предполагаю, что новые функции, которые люди пишут на С++ 11, обычно принимают параметры std::function, потому что таким образом, функция не должна быть шаблоном, или она не должна быть создана для каждого одиночный лямбда и функтор, который передается ему.

Почему параметр std::function (или указатель на функцию) не принимает значения по умолчанию?

Не очевидно, какой тип такой функции будет.

  • Если это std::function<int(bool)>, то как вы называете его по умолчанию? (Вы не можете.)

  • Если это std::function<int(bool=false)>, то с какими типами он совместим и как работает преобразование? Можете ли вы преобразовать его в std::function<int()>? Что насчет std::function<int(bool=true)>?

  • Если это что-то новое как std::function<int(bool=default)>, то с какими типами он совместим и как работает преобразование?

В принципе, это не просто переключатель, который вы можете перевернуть в стандарте и сделать указатели на функции / std::function обрабатывать аргументы по умолчанию. Аргументы по умолчанию в обычных функциях обрабатываются с использованием информации из объявления функции, которая недоступна на сайте вызова для указателя лямбда или функции. Поэтому вам придется кодировать информацию о значениях по умолчанию в тип функции, а затем выработать все неочевидные правила для преобразования и совместимости.

Итак, вам придется придумать убедительный аргумент в пользу того, почему такая функция будет добавлена, и убедить комитет.

Итак, почему lambdas не может принимать значения по умолчанию?

Я не ответил на этот вопрос. Но я не думаю, что это была бы очень полезная функция. Я бы удалил этот ответ, если бы мог, но он был принят. Я бы сделал это, если бы мог, но это мое. C'est la vie.

Ответ 2

Как следует из комментариев к вопросу, нет, вероятно, технической причины, по которой не было аргументов по умолчанию. Но вопрос: "Есть ли практическая причина аргументов по умолчанию?" Я думаю, что ответ на этот вопрос "нет", и вот почему.

Чтобы вызвать лямбда, вы можете просто позвонить ему прямо сейчас

[] { printf("foo"); }();

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

  • Использовать авто. Итак, получим auto foo = [] { printf("foo"); }; foo();
  • Привяжите его к указателю функции: void (*foo)() = [] { printf("foo"); }; foo(). Конечно, это работает, только если лямбда не захватывает.
  • Сделайте эквивалент 1 или 2, передав лямбда в шаблон функции или функции.

Теперь давайте рассмотрим полезность аргументов по умолчанию в каждом случае

  • Мы вызываем лямбда прямо в этом случае, поэтому код, вероятно, достаточно узкий, что мы не будем называть лямбда с различным количеством аргументов. Если да, то лямбда могла (должна?), Вероятно, быть реорганизована в более общую составляющую. Я не вижу здесь никакой практической пользы.
  • (см. 1)
  • Мы передаем лямбду на другую функцию. Я также не вижу практического преимущества аргумента по умолчанию. Вспомните добрые старые функторы (которые могут иметь аргументы по умолчанию). Я не могу сказать, что знаю слишком много функций, которые учитывают аргументы по умолчанию, присутствующие даже для них. Поскольку лямбды являются фактически просто функторами, нет никаких оснований для внезапного изменения этого наблюдения.

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

void func(int i = 0)
{
}

и возьмите его адрес. Что вы получаете? A void (*)(int). Нет причин, чтобы лямбда следовала различным правилам.

Ответ 3

Я согласен с тем, что нет реального "технического" ограничения как такового, чтобы позволить аргументам по умолчанию в lambdas работать в некоторых случаях. Это не повлияет на ваши указатели и auto, потому что на тип функции не влияют аргументы по умолчанию. Но это также объясняет, почему это было бы непрактично.

Почему?

Поскольку аргументы по умолчанию, являющиеся частью сигнатуры функции, не являются частью типа функции:

[C++11: 1.3.17]:
подпись
< & функция GT; имя, список типов параметров (8.3.5) и охватывающее пространство имен (если есть)
[Примечание. Подписи используются в качестве основы для манипулирования именами и связывания. -end note]

[C++11: 8.3.5/6]: [..] Тип возврата, список-тип параметра, ref-qualifier и cv-qualifier-seq, но не аргументы по умолчанию (8.3.6) или спецификация исключения (15.4), являются частью типа функции. [Примечание. Типы функций проверяются при назначении и инициализации указателей на функции, ссылки на функции и указатели на функции-члены. -end note]

Они по сути являются частью синтаксического сахара, которые "активируются" компилятором, способным видеть объявление функции, которую вы используете, и вводить в точке вызова функции:

#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

int main()
{
   foo();
}
  • Выход: 5

Аргумент по умолчанию "visible".

Однако, когда вы скрываете свою функцию за указателем:

int main()
{
    void (*bar)(int) = &foo;
    bar();
}
  • Ошибка: too few arguments to function

Тип bar правильный, и компилятор знает, что foo имеет значение по умолчанию, но просто нет прямого синтаксиса, который существует, чтобы сообщить компилятору в точке вызова bar, что bar также foo. Конечно, в этом тривиальном сценарии он мог бы понять это, наблюдая за заданием, но это вряд ли оправдывает более широкий аргумент.

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

// a.h
void foo(int);

// a.cpp
#include "a.h"
#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

// main.cpp
#include "a.h"

int main()
{
    foo();
}
  • Ошибка в main.cpp: too few arguments to function

Я предполагаю, что это причина:

[C++11: 8.3.6/4]: [..] Объявления в разных областях имеют совершенно разные наборы аргументов по умолчанию. [..]

Нам разрешено "накапливать" аргументы по умолчанию для определения функций члена без шаблона ([C++11 8.3.6/6]); пример показывает, что это значение по умолчанию будет применяться только в том же TU, что следует за поведением, которое мы видели выше в моем втором фрагменте кода.

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