Vraag Wat betekent het om van lambda te erven?


Vond deze code die interessant lijkt:

auto a = [](){};

class B : decltype(a)
{
};

Ik wil weten wat het doet. Kan dit op wat voor manier dan ook nuttig zijn?


60
2018-06-25 01:11


oorsprong


antwoorden:


Welnu, die code zal worden gecompileerd, maar het probleem is dat dit niet mogelijk is standaard construeer elk voorwerp van die klasse1, omdat de bouwer van de lambda is niet toegankelijk (andere dan constructeurs kopiëren / verplaatsen). De enige constructeurs gegarandeerd door een lambda type is a gebreke constructeur kopiëren / verplaatsen. En er is geen standaardconstructor

[Expr.prim.lambda / 21]

Het sluitingstype geassocieerd met een lambda-expressie heeft geen standaardwaarde   constructor en een operator voor verwijderde kopieertoewijzing. Het heeft een standaard   copy constructor en een standaard verhuisconstructor ([class.copy]). [   Opmerking: deze speciale lidfuncties worden impliciet gedefinieerd zoals gebruikelijk,   en kan daarom worden gedefinieerd als verwijderd. - eindnoot]

of van cppreference:

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

De geschiedenis van de lambda-constructor die ontoegankelijk is, dateert uit zijn vroege dagen hier

In sectie 3, tweede alinea, citeer ik:

In deze vertaling, __some_unique_name is een nieuwe naam, niet gebruikt   elders in het programma op een manier die conflicten met zijn   gebruik als een sluitingstype. Deze naam en de constructeur voor de klas,   hoeven niet te worden blootgesteld aan de gebruiker - de enige functies die de gebruiker heeft   kunnen vertrouwen op in het type sluiting zijn een kopie constructor (en een zet   constructeur als dat voorstel wordt goedgekeurd) en de functieaanroep   operator. Sluitingssoorten hebben geen standaardconstructors, toewijzing nodig   operatoren of andere toegangsmiddelen dan functieaanroepen. Het kan   de moeite waard zijn voor de implementeerbaarheid om het maken van afgeleide klassen te verbieden   van sluitingstypes. ...

Zoals je ziet, stelde het voorstel zelfs voor dat het creëren van afgeleide klassen van sluitingstypen verboden zou moeten zijn.


1je kunt natuurlijk de basisklasse kopiëren met a om een ​​object van het type te initialiseren B. Zien deze


Nu, op uw vraag:

Kan dit op wat voor manier dan ook nuttig zijn?

Niet in jouw exacte vorm. Je zult alleen instantiable zijn met de instantie a. Als u echter erven van een generieke Callable Class zoals een lambda-type, zijn er twee gevallen die ik kan bedenken.

  1. Maak een Functor die een groep functoren in een bepaalde overervingsreeks oproept:

    Een vereenvoudigd voorbeeld:

    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...);
    }
    

    voorbeeld gebruik:

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

    uitgang

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

    Het zien Live op Coliru

  2. Maak een Functor met een overbelaste operator()(...), overbelast met alle basisklassen ' operator()(...):

    • Nir Friedman heeft er al een goed voorbeeld van gegeven in zijn antwoord op deze vraag.
    • Ik heb ook een soortgelijk en vereenvoudigd voorbeeld opgesteld, getrokken uit het Zijne. Het zien op Coliru
    • Jason Lucas demonstreerde ook zijn praktische toepassingen in zijn CppCon 2014 presentatie "Polymorphism with Unions". Je kunt de Repo vinden hier, een van de exacte locatie in de broncode hier (Bedankt Cameron DaCamara)

Nog een coole truc: sinds het resulterende type uit make_functionSequence(...) is een opvraagbare klasse. Je kunt op een later tijdstip nog meer lambda's of callable toevoegen.

    //.... 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);

33
2018-06-25 02:08



lambda zijn functie objecten eronder, met extra syntatische suiker. a evalueert naar zoiets (met de MyLambda naam is een willekeurige naam, net zoals wanneer je het maakt namespace {} - naamruimte naam is willekeurig):

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

Dus als je erven van een lambda, is wat je doet het erven van een anonieme klasse / structuur.

Wat betreft het nut, nou, het is net zo nuttig als elke andere erfenis. Je kunt de functionaliteit van meerdere lambda's met meerdere overerving in één object hebben, je kunt er nieuwe methoden aan toevoegen om het uit te breiden. Ik kan op dit moment geen echte toepassing bedenken, maar ik weet zeker dat er veel zijn.

Verwijzen naar deze vraag voor meer informatie.


8
2018-06-25 01:31



Dit kan best heel handig zijn, maar het hangt ervan af hoe direct je wilt zijn over het hele ding. Beschouw de volgende code:

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

Het fragment in uw vraag verschijnt nergens in exacte vorm. Maar je kunt zien dat de soorten lambda's worden afgeleid apply_visitor_inline. Een klasse wordt dan geïnstantieerd die erft van al deze lambda's. De bedoeling? We zijn in staat om meerdere lambda's te combineren tot één enkele, met als doel dingen als apply_visitor. Deze functie verwacht een enkel functieobject te ontvangen dat meerdere definieert operator() en onderscheid maken tussen hen op basis van overbelasting. Maar soms is het handiger om een ​​lambda te definiëren die werkt op elk van de soorten die we moeten dekken. In dat geval biedt erfenis van lambda's een combinatiemiddel.

Ik heb het inline-bezoekersidee vanaf hier gekregen: https://github.com/exclipy/inline_variant_visitor, hoewel ik niet naar de implementatie daar heb gekeken, dus deze implementatie is de mijne (maar ik denk dat het zeer vergelijkbaar is).

Bewerken: de oorspronkelijk geposte code werkte alleen vanwege een bug in clang. Volgens deze vraag (Overladen lambda's in C ++ en verschillen tussen clang en gcc), de zoekopdracht voor meerdere operator() in basis klassen is dubbelzinnig, en inderdaad compileerde de code die ik gepost oorspronkelijk niet in gcc. De nieuwe code compileert in beide en zou compliant moeten zijn. Helaas is er geen manier om een ​​variadische verklaring te gebruiken, dus recursie moet worden gebruikt.


6
2018-06-25 02:11