c++ references O que significa herdar de lambda?



pass lambda to function (3)

Lambdas são objetos de função embaixo, com açúcar sintático adicional. a avalia algo assim (com o nome MyLambda sendo um nome aleatório, assim como quando você faz o namespace {} - o nome do namespace será aleatório):

class MyLambda {
public:
    void operator()() {
    }
}

Então, quando você herda de um lambda, o que você está fazendo é herdar de uma classe / estrutura anônima.

Quanto à utilidade, bem, é tão útil quanto qualquer outra herança. Você pode ter a funcionalidade de vários lambdas com herança múltipla em um objeto, você pode adicionar novos métodos para estendê-lo. Não consigo pensar em nenhuma aplicação real no momento, mas tenho certeza de que há muitos.

Consulte esta questão para mais informações.

Encontrei este código que parece ser interessante:

auto a = [](){};

class B : decltype(a)
{
};

Eu quero saber o que isso faz. Isso pode ser útil de alguma forma?


Answer #1

Bem, esse código irá compilar, mas o problema é que você será incapaz de construir qualquer objeto da classe 1 , porque o construtor do lambda não é acessível (além de construtores copy / move) . Os únicos construtores garantidos por um tipo lambda são um construtor de cópia / movimentação padrão . E não há nenhum construtor padrão

[expr.prim.lambda/21]

O tipo de fechamento associado a uma expressão lambda não possui um construtor padrão e um operador de atribuição de cópia excluído. Ele tem um construtor de cópia padrão e um construtor de movimento padrão ([class.copy]). [Nota: Essas funções de membro especiais são implicitamente definidas como de costume e, portanto, podem ser definidas como excluídas. - nota final

ou da cppreference :

//ClosureType() = delete;                     //(until C++14)
ClosureType(const ClosureType& ) = default;   //(since C++14)
ClosureType(ClosureType&& ) = default;        //(since C++14)

A história do construtor lambda sendo inacessível remonta à sua proposta inicial, encontrada here

Na secção 3, segundo parágrafo, cito:

Nessa conversão, __some_unique_name é um novo nome, não usado em outras partes do programa, de forma a causar conflitos com seu uso como tipo de fechamento. Esse nome e o construtor da classe não precisam ser expostos ao usuário - os únicos recursos em que o usuário pode confiar no tipo de fechamento são um construtor de cópia (e um construtor de movimento, se essa proposta for aprovada) e o operador de chamada de função. Os tipos de fechamento não precisam de construtores padrão, operadores de atribuição ou qualquer outro meio de acesso além das chamadas de função. Pode valer a pena implementar para implementar classes derivadas de tipos de fechamento. ...

Como você pode ver, a proposta até sugeriu que a criação de classes derivadas de tipos de fechamento deveria ser proibida.

É claro que você pode copiar-inicializar a classe base com a fim de inicializar um objeto do tipo B Veja this

Agora, para sua pergunta:

Isso pode ser útil de alguma forma?

Não está na sua forma exata. Você só será instanciável com a instância a . No entanto, se você herdar de uma classe Callable genérica, como um tipo lambda, há dois casos em que posso pensar.

  1. Crie um Functor que chame um grupo de functores em uma determinada sequência de herança:

    Um exemplo simplificado:

    template<typename TFirst, typename... TRemaining>
    class FunctionSequence : public TFirst, FunctionSequence<TRemaining...>
    {
        public:
        FunctionSequence(TFirst first, TRemaining... remaining)
            : TFirst(first), FunctionSequence<TRemaining...>(remaining...)
        {}
    
        template<typename... Args>
        decltype(auto) operator () (Args&&... args){
            return FunctionSequence<TRemaining...>::operator()
                (    TFirst::operator()(std::forward<Arg>(args)...)     );
        }
    };
    
    template<typename T>
    class FunctionSequence<T> : public T
    {
        public:
        FunctionSequence(T t) : T(t) {}
    
        using T::operator();
    };
    
    
    template<typename... T>
    auto make_functionSequence(T... t){
        return FunctionSequence<T...>(t...);
    }

    exemplo de uso:

    int main(){
    
        //note: these lambda functions are bug ridden. Its just for simplicity here.
        //For correct version, see the one on coliru, read on.
        auto trimLeft = [](std::string& str) -> std::string& { str.erase(0, str.find_first_not_of(' ')); return str; };
        auto trimRight = [](std::string& str) -> std::string& { str.erase(str.find_last_not_of(' ')+1); return str; };
        auto capitalize = [](std::string& str) -> std::string& { for(auto& x : str) x = std::toupper(x); return str; };
    
        auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);
        std::string str = " what a Hullabaloo     ";
    
        std::cout << "Before TrimAndCapitalize: str = \"" << str << "\"\n";
        trimAndCapitalize(str);
        std::cout << "After TrimAndCapitalize:  str = \"" << str << "\"\n";
    
        return 0;
    }

    saída

    Before TrimAndCapitalize: str = " what a Hullabaloo     "
    After TrimAndCapitalize:  str = "WHAT A HULLABALOO"

    Veja-o ao vivo em Coliru

  2. Crie um Functor com um operator()(...) sobrecarregado operator()(...) , sobrecarregado com todas as classes base ' operator()(...) :

    • Nir Friedman já deu um bom exemplo disso em sua answer a essa pergunta.
    • Eu também elaborei um exemplo similar e simplificado, extraído do Seu. Veja em Coliru
    • Jason Lucas também demonstrou suas aplicações práticas em sua apresentação do CppCon 2014 "Polymorphism with Unions" . Você pode encontrar o Repo here , um dos locais exatos no código fonte here (Obrigado Cameron DaCamara)

Outro truque legal: como o tipo resultante de make_functionSequence(...) é uma classe que pode ser chamada. Você pode acrescentar mais lambda's ou chamá-las posteriormente.

    //.... As previously seen

    auto trimAndCapitalize = make_functionSequence(trimLeft, trimRight, capitalize);

    auto replace = [](std::string& str) -> std::string& { str.replace(0, 4, "Whaaaaat"); return str; };

    //Add more Functors/lambdas to the original trimAndCapitalize
    auto replaced = make_functionSequence(trimAndCapitalize, replace /*, ... */);
    replaced(str2);

Answer #2

Isso pode realmente ser bastante útil, mas depende de quão direto você quer ser sobre a coisa toda. Considere o seguinte código:

#include <boost/variant.hpp>

#include <iostream>
#include <string>
#include <unordered_map>

template <class R, class T, class ... Ts>
struct Inheritor : public  T, Inheritor<R, Ts...>
{
  using T::operator();
  using Inheritor<R, Ts...>::operator();
  Inheritor(T t, Ts ... ts) : T(t), Inheritor<R, Ts...>(ts...) {}
};

template <class R, class T>
struct Inheritor<R, T> : public boost::static_visitor<R>, T
{
  using T::operator();
  Inheritor(T t) : T(t) {}
};

template <class R, class V, class ... T>
auto apply_visitor_inline(V& v, T ... t)
{
  Inheritor<R, T...> i(t...);
  return boost::apply_visitor(i, v);
}

int main()
{
  boost::variant< int, std::string > u("hello world");
  boost::variant< int, std::string > u2(5);

  auto result = apply_visitor_inline<int64_t>(u, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  auto result2 = apply_visitor_inline<int64_t>(u2, [] (int i) { return i;}, [] (const std::string& s) { return s.size();});
  std::cout << result;
  std::cout << result2;
}

O snippet da sua pergunta não aparece na forma exata em qualquer lugar. Mas você pode ver que os tipos de lambdas estão sendo inferidos em apply_visitor_inline . Uma classe é então instanciada que herda de todos esses lambdas. O objetivo? Somos capazes de combinar vários lambdas em um único, com o propósito de coisas como apply_visitor . Essa função espera receber um único objeto de função que define vários operator() e distinguir entre eles com base na sobrecarga. Mas às vezes é mais conveniente definir um lambda que opera em cada um dos tipos que temos que cobrir. Nesse caso, a herança de lambdas fornece um mecanismo para combinar.

Eu tenho a idéia inline visitante aqui: https://github.com/exclipy/inline_variant_visitor , embora eu não olhe para a implementação lá, por isso esta implementação é minha (mas eu acho que é muito semelhante).

Edit: o código originalmente postado só funcionou devido a um bug no clang. De acordo com essa questão ( Sobrecargas lambdas em C ++ e diferenças entre clang e gcc ), a pesquisa por multiple operator() em classes base é ambígua e, de fato, o código que eu postei originalmente não compila no gcc. O novo código é compilado em ambos e deve ser compatível. Infelizmente, não há nenhuma maneira aparentemente fazer uma instrução variadic usando, então a recursão deve ser usada.





c++