C ++ 11 и отсутствие полиморфных лямбд - почему? - PullRequest
36 голосов
/ 10 января 2011

Я просматривал черновую версию стандарта C ++ 11 . В частности, раздел о лямбдах , и я не совсем понимаю, почему не следует вводить полиморфные лямбды.

Например, среди 100001 способов использования полиморфных лямбд, я надеялся, что мы сможем использовать такой код:

template<typename Container>
void foo(Container c)
{
    for_each(c.begin(), c.end(), [](T& t) { ++t; });
}

Каковы были причины:

  • Неужели у комитета не осталось времени?

  • Что полиморфные лямбды слишком сложны для реализации?

  • Или, возможно, они не нужны PTB ?

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

Связанные источники:

Ответы [ 5 ]

51 голосов
/ 21 декабря 2011

Причина, по которой у нас нет полиморфных лямбд, довольно хорошо объясняется в этой публикации .

Это связано с концепцией, которая была взята из C ++ 11: по существуполиморфные лямбды - это обычные шаблоны функций без ограничений, и мы не знали, как проверить типо-ограниченный шаблон, который использовал шаблон без ограничений.Тем не менее, решение этой проблемы оказывается простым, как показано здесь (мертвая ссылка), поэтому я не думаю, что осталось какое-либо препятствие.

Ссылка на cpp-next не работает;соответствующую информацию можно найти здесь

16 голосов
/ 10 января 2011

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

template<typename Container>
void foo(Container c)
{
    for_each(c.begin(), c.end(),[](typename Container::reference t) { ++t; });
}

Я также продемонстрируюПриведенный выше комментарий Джона Пурди, который является еще одним способом получить нужное вам имя в этой лямбде:

template<typename Container>
void foo(Container c)
{
   for_each(c.begin(),c.end(),[](decltype(*c.begin()) t) { ++t; });
}

(Да, Доминар, я знаю, вам не нравится этот ответ, потому что он не 'Я не могу ответить на ваш вопрос, но я готов поспорить, что следующий человек, который придет задавать этот вопрос, будет искать способ заставить свой код работать, так что имеет смысл иметь некоторые приемы, где вопросотношение.)

7 голосов
/ 10 января 2011

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

Я действительно не понимаюМне нравится предложенный синтаксис.T является ключевым словом?Все ли идентификаторы, для которых не удалось найти имя, автоматически превращаются в аргументы типа имени шаблона?Это предотвращает обнаружение орфографических ошибок, что, по мнению IMO, BAD идея:

for_each(c.begin(),c.end(),[](iterater& t) { ++t; });
// programmer misspelled "iterator" and now has a polymorphic lambda, oops

Оно также вводит поведение на расстоянии, если именованный тип вводится в некоторый заголовокфайл где-то, значение внезапно меняется.Также действительно ПЛОХО .

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

for_each(c.begin(),c.end(),[]template<typename T>(T& t) { ++t; });

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

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

for_each(c.begin(),c.end(),[](auto& t) { ++t; });

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

Но теперь у вас есть проблема, из-за которой for_each выводитАргумент шаблона typename, а не аргумент шаблона шаблона.Вывод типа невозможен в этом контексте.

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

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

#include <vector>
#include <algorithm>
#include <iterator>

int main(void)
{
    using namespace std;
    vector<int> a(10);
    vector<int> b(10);
    vector<int> results;

    transform(a.begin(), a.end(), b.begin(), back_inserter(results), min<int>);
}

Параметр типа шаблона для std::min должен быть указан явно.Лямбды ничем не отличаются от использования существующих функторов в этом отношении.

РЕДАКТИРОВАТЬ: Хорошо, теперь, когда я понимаю, мы не предлагаем, чтобы лямбда генерировала тип функтора шаблона, а единственный тип не функтора шаблона, который осуществляетоператор приложения шаблонной функции (operator()()), я согласен, что компилятор должен быть в состоянии генерировать такую ​​вещь.Я предлагаю, чтобы использование ключевого слова auto было бы хорошим простым синтаксисом для запроса этого.

Однако я не очень доволен и auto.А как насчет лямбд с несколькими параметрами:

[](auto& x, auto& y){ return x + y; }
//becomes
template<typename T1, typename T2>
auto operator()(T1& x, T2& y) -> decltype(x + y) { return x + y; }

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

[](auto& x, decltype(x)& y){ return x + y; }
//becomes
template<typename T1>
auto operator()(T1& x, T1& y) -> decltype(x + y) { return x + y; }

Кажется, хорошо, но янайти синтаксис, вводящий в заблуждение.Синтаксис предполагает, что параметр типа выводится из первого фактического параметра, а второй параметр приводится к одному и тому же типу, но на самом деле оба фактических параметра считаются равными во время вывода типа.

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

3 голосов
/ 10 января 2011

Ну, теперь, когда вы связались n1968 , ответ на ваш вопрос очевиден. Он найден в разделе 5.1 предложения.

0 голосов
/ 10 января 2011

следующий (ваш комментарий к моему другому ответу выше) работает:

#include <algorithm>
#include <vector>

struct foo
{
   template<typename T>
   void operator()(T& t)
   {
      ++t;
   }
};

int main()
{

   std::vector<int> v;
   std::for_each(v.begin (),v.end(),foo());

   return 0;
}

Но следующее не работает:

#include <algorithm>
#include <vector>

template<typename T>
struct foo
{
   void operator()(T& t)
   {
      ++t;
   }
};

int main()
{

   std::vector<int> v;
   std::for_each(v.begin (),v.end(),foo()); // <-- the syntax for foo here 
                                            //     is kinda fictitious

   return 0;
}

Вероятно, комитет C ++видел лямбды как более похожие на второй пример, чем первый.(Хотя я не нашел умного способа определить лямбду, в которой это имело бы значение. У кого-нибудь есть какие-нибудь сумасшедшие идеи?)

...