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

С++ 11 и отсутствие полиморфных лямбда - почему?

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

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

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

В чем были причины:

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

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

  • Или возможно, что они считаются не требуемыми PTB?

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

Поскольку аргумент 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; });
}

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

Ответ 3

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

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

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

Он также вводит поведение "на расстоянии", если именованный тип появляется где-то в каком-либо файле заголовка, значение меняется внезапно. Также на самом деле BAD.

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

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 содержит аргумент шаблона шаблона, а не аргумент шаблона шаблона. В этом контексте вывод типа невозможен.

В текущем предложении lambdas имеют тип, даже если он является типом без названия (кроме 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 должен быть явно указан. Lambdas ничем не отличается от использования существующих функторов в этом отношении.

EDIT: Хорошо, теперь я понимаю, что мы не предполагаем, что лямбда-генерация типа шаблона-шаблона, но один тип шаблона без шаблона, который реализует оператор приложения с шаблонами функций (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; }

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

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

Ответ 4

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

Ответ 5

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

#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;
}

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