Почему я не могу создать вектор лямбда-выражений (одного типа) в C ++ 11?

88

Я пытался создать вектор лямбда, но не смог:

auto ignore = [&]() { return 10; };  //1
std::vector<decltype(ignore)> v;     //2
v.push_back([&]() { return 100; });  //3

До строки №2 компилируется нормально . Но строка №3 дает ошибку компиляции :

ошибка: нет соответствующей функции для вызова 'std :: vector <main () :: <lambda () >> :: push_back (main () :: <lambda ()>)'

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

Наваз
источник
23
«Мне не нужен вектор указателей на функции или вектор объектов функций». Но это то, о чем вы просили. Лямбда - это функциональный объект.
Никол Болас

Ответы:

135

Каждая лямбда имеет разный тип, даже если у них одна и та же сигнатура. Вы должны использовать инкапсулирующий контейнер времени выполнения, например, std::functionесли вы хотите сделать что-то подобное.

например:

std::vector<std::function<int()>> functors;
functors.push_back([&] { return 100; });
functors.push_back([&] { return  10; });
Щенок
источник
52
Управление командой разработчиков из ста человек для меня больше похоже на кошмар :)
Джереми Фриснер
10
Также не забывайте, что лямбда-выражения без захвата ([] -стиль) могут превратиться в указатели на функции. Таким образом, он мог хранить массив указателей на функции одного и того же типа. Обратите внимание, что в VC10 это еще не реализовано.
Никол Болас
Кстати, не следует ли вообще использовать в этих примерах без захвата? Или это необходимо? - Кстати, лямбда без захвата для указателя на функцию, похоже, поддерживается в VC11. Хотя не проверял.
Klaim
2
Можно ли создать вектор, хранящий функции разного типа? то есть, вместо того, чтобы ограничиваться std::function<int(), могу ли я использовать разные прототипы функций?
manatttta
2
@manatttta В чем будет смысл? Контейнеры существуют для хранения объектов одного типа, для организации и управления ими вместе. Вы также можете спросить: «Могу ли я создать vectorхранилище и std::functionи std::string?» И ответ тот же: нет, потому что это не по назначению. Вы можете использовать класс в стиле «вариант» для выполнения стирания типа, достаточного для помещения несопоставимых вещей в контейнер, в то же время включая метод, позволяющий пользователю определить «настоящий» тип и, следовательно, выбрать, что с ним делать (например, как вызвать) каждый элемент ... но опять же, зачем так далеко? Есть ли какое-нибудь реальное объяснение?
underscore_d
40

Все лямбда-выражения имеют разный тип, даже если они посимвольно идентичны . Вы вставляете лямбду другого типа (потому что это другое выражение) в вектор, и это явно не сработает.

Одно из решений - std::function<int()>вместо этого создать вектор .

auto ignore = [&]() { return 10; };
std::vector<std::function<int()>> v;
v.push_back(ignore);
v.push_back([&]() { return 100; });

С другой стороны, это не лучшая идея, [&]если вы ничего не захватываете.

Р. Мартиньо Фернандес
источник
14
Не нужны ()лямбды, которые не принимают аргументов.
Puppy
18

Хотя то, что другие сказали, актуально, все еще можно объявить и использовать вектор лямбда, хотя это не очень полезно:

auto lambda = [] { return 10; };
std::vector<decltype(lambda)> vec;
vec.push_back(lambda);

Таким образом, вы можете хранить там любое количество лямбд, если это копия / перемещение lambda!

Люк Дантон
источник
Что на самом деле может быть полезно, если возврат происходит в цикле с другими параметрами. Предположительно для ленивых оценочных целей.
MaHuJa
7
Нет, вы не помещаете параметры в вектор, а только в объект функции ... Так что это будет вектор со всеми копиями одной и той же лямбды
hariseldon78
16

Если ваша лямбда не имеет состояния, т. Е. [](...){...}C ++ 11 позволяет ей преобразоваться в указатель на функцию. Теоретически компилятор, совместимый с C ++ 11, сможет это скомпилировать:

auto ignore = []() { return 10; };  //1 note misssing & in []!
std::vector<int (*)()> v;     //2
v.push_back([]() { return 100; });  //3
MSN
источник
4
Для записи auto ignore = *[] { return 10; };сделает . ignoreint(*)()
Люк Дантон
1
@ Люк, это мерзко! Когда они это добавили?
MSN
3
Что ж, поскольку функция преобразования, которая позволяет принимать указатель на функцию в первую очередь, обязана не быть explicit, разыменование лямбда-выражения является допустимым и разыменовывает указатель, полученный в результате преобразования. Затем с помощью autoраспада этой ссылки обратно в указатель. (Используя auto&или auto&&сохранил бы ссылку.)
Люк Дантон
Ах ... Разыменование полученного указателя. Это имеет смысл. Пропуск был ()намеренным или случайным?
MSN
Умышленно лямбда-выражение эквивалентно (но на два символа короче).
Люк Дантон
6

Вы можете использовать функцию генерации лямбда (обновлено исправлением, предложенным Nawaz):

#include <vector>
#include <iostream>

int main() {
    auto lambda_gen = [] (int i) {return [i](int x){ return i*x;};} ;

    using my_lambda = decltype(lambda_gen(1));

    std::vector<my_lambda> vec;

    for(int i = 0; i < 10; i++) vec.push_back(lambda_gen(i));

    int i = 0;

    for (auto& lambda : vec){
        std::cout << lambda(i) << std::endl;
        i++;
    }
}

Но я думаю, что на этом этапе вы в основном создали свой собственный класс. В противном случае, если лямбды имеют совершенно разные caputres / args и т. Д., Вам, вероятно, придется использовать кортеж.

допотопный
источник
Хорошая идея обернуть его в функцию, lambda_genкоторая, в свою очередь, может быть лямбдой. Однако auto a = lambda_gen(1);делает ненужный вызов, которого можно избежать, если мы напишем это decltype(lambda_gen(1)).
Nawaz
Но разве это не лишний вызов? Также еще один незначительный момент заключается в том, что в вопросе говорится о С ++ 11, поэтому, как мне кажется, нужно добавить конечный возвращаемый тип к функции.
допотопный
Нет. Все, что decltype находится внутри, не оценивается , поэтому вызов на самом деле не производится. То же самое и с sizeof. Кроме того, этот код не будет работать в C ++ 11, даже если вы добавите конечный возвращаемый тип !!
Nawaz
4

Каждая лямбда - это отдельный тип. Вы должны использовать std::tupleвместо std::vector.

Пол Фульц II
источник