Como o lambda genérico funciona em C ++ 14?

114

Como o lambda genérico funciona ( autopalavra-chave como tipo de argumento) no padrão C ++ 14?

É baseado em modelos C ++ onde, para cada tipo de argumento diferente, o compilador gera uma nova função com o mesmo corpo, mas com tipos substituídos (polimorfismo em tempo de compilação) ou é mais semelhante aos genéricos de Java (eliminação de tipo)?

Exemplo de código:

auto glambda = [](auto a) { return a; };
Sasha.sochka
fonte
6
Corrigido para C ++ 14, originalmente usado C ++ 11 em questão
sasha.sochka

Respostas:

130

Lambdas genéricos foram introduzidos em C++14.

Simplesmente, o tipo de fechamento definido pela expressão lambda terá um templated operador de chamada em vez do, sem gabarito operador de call regular de C++11lambdas 's (claro, quando autoaparece pelo menos uma vez na lista de parâmetros).

Então, seu exemplo:

auto glambda = [] (auto a) { return a; };

Fará glambdauma instância deste tipo:

class /* unnamed */
{
public:
    template<typename T>
    T operator () (T a) const { return a; }
};

O parágrafo 5.1.2 / 5 do Projeto Padrão C ++ 14 n3690 especifica como o operador de chamada do tipo de fechamento de uma dada expressão lambda é definido:

O tipo de fechamento para uma expressão lambda não genérica tem um operador de chamada de função inline pública (13.5.4) cujos parâmetros e tipo de retorno são descritos pela cláusula de declaração de parâmetro e tipo de retorno final da expressão lambda, respectivamente. Para um lambda genérico, o tipo de fechamento tem um modelo de membro do operador de chamada de função inline pública (14.5.2), cuja lista de parâmetros de modelo consiste em um parâmetro de modelo de tipo inventado para cada ocorrência de auto na cláusula de declaração de parâmetro do lambda, em ordem de aparência. O parâmetro-modelo do tipo inventado é um pacote de parâmetros se a declaração de parâmetro correspondente declara um pacote de parâmetros de função (8.3.5). O tipo de retorno e os parâmetros de função do modelo de operador de chamada de função são derivados do tipo de retorno final e da cláusula de declaração de parâmetro da expressão lambda, substituindo cada ocorrência de auto nos especificadores decl da cláusula de declaração de parâmetro com o nome de o parâmetro-modelo inventado correspondente.

Finalmente:

É semelhante aos modelos em que, para cada tipo de argumento diferente, o compilador gera funções com o mesmo corpo, mas com tipos alterados, ou é mais semelhante aos genéricos do Java?

Como o parágrafo acima explica, lambdas genéricos são apenas açúcar sintático para functores únicos e não nomeados com um operador de chamada modelado. Isso deve responder a sua pergunta :)

Andy Prowl
fonte
7
No entanto, eles também permitem uma classe definida localmente com um método de modelo. O que é novo.
Yakk - Adam Nevraumont
2
@Yakk: A restrição para modelos locais de função já não foi eliminada com o C ++ 11?
Sebastian Mach
2
@phresnel: Não, essa restrição não foi levantada
Andy Prowl
1
@AndyProwl: Percebo meu erro. O que foi levantado de fato foi o uso de tipos locais como argumentos de modelo (como em int main () { struct X {}; std::vector<X> x; })
Sebastian Mach
1
@phresnel: Certo, isso realmente mudou
Andy Prowl
25

Infelizmente , eles não fazem parte do C ++ 11 ( http://ideone.com/NsqYuq ):

auto glambda = [](auto a) { return a; };

int main() {}

Com g ++ 4.7:

prog.cpp:1:24: error: parameter declared auto
...

No entanto , a forma como pode ser implementado em C ++ 14 de acordo com a proposta de Portland para lambdas genéricos :

[](const& x, & y){ return x + y; }

Isso resultaria na maior parte da criação usual de uma classe de functor anônima, mas com a falta de tipos, o compilador emitiria um membro modelado- operator():

struct anonymous
{
    template <typename T, typename U>
    auto operator()(T const& x, U& y) const -> decltype(x+y)
    { return x + y; }
};

Ou de acordo com a proposta mais recente Proposta para Expressões Lambda Genéricas (Polimórficas)

auto L = [](const auto& x, auto& y){ return x + y; };

--->

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;

Então, sim, para cada permutação de parâmetros, uma nova instanciação surgiria, no entanto, os membros desse functor ainda seriam compartilhados (ou seja, os argumentos capturados).

Sebastian Mach
fonte
6
Essa proposta de permitir a eliminação do especificador de tipo é totalmente grotesca.
Lightness Races in Orbit
Eles entraram com g ++ - 4.9 . Você precisa fornecer -std=c++1y.
emsr
Não sabia que o ideone ainda não tinha gcc-4.9 e C ++ 14.
emsr
pergunta: este autotem as mesmas regras de dedução do automóvel clássico? Se nos referirmos à analogia do modelo, isso significaria que o automático não é automático, são as mesmas regras da dedução do tipo de modelo. Então a questão é: a dedução do modelo é equivalente a auto?
v.oddou
@ v.oddou: "Classic auto" é bom. Para mim, "classic auto" significa "Stack Variable", e já foi usado em contraste com staticou register:) De qualquer forma, sim, usar autosignifica que, por baixo do capô, um modelo normal é gerado. Na verdade, um lambda será substituído internamente pelo compilador por uma classe de functor, e um autoparâmetro significa que template <T> ... (T ...)será emitido.
Sebastian Mach
17

É um recurso proposto do C ++ 14 (não no C ++ 11) semelhante (ou mesmo equivalente) aos modelos. Por exemplo, N3559 fornece este exemplo:

Por exemplo, esta expressão lambda genérica contendo a instrução:

auto L = [](const auto& x, auto& y){ return x + y; };

pode resultar na criação de um tipo de fechamento e objeto que se comporta de forma semelhante à estrutura abaixo:

struct /* anonymous */
{
    template <typename T, typename U>
    auto operator()(const T& x, U& y) const // N3386 Return type deduction
    { return x + y; }
} L;
Cassio Neri
fonte