66 votes

Que signifie hériter de lambda?

Trouvé ce code qui semble être intéressant:

 auto a = [](){};

class B : decltype(a)
{
};
 

Je veux savoir ce que ça fait. Est-ce que cela peut être utile?

37voto

WhiZTiM Points 3351

Eh bien, ce code compile, mais le problème est que vous ne pouvez pas par défaut de la construction de tout objet de la classe1, parce que le constructeur de la lambda n'est pas accessible (autres que de copier/déplacer des constructeurs). Les seuls constructeurs garanti par un lambda type est un défaut copier/déplacer constructeur. Et il n'y a pas de constructeur par défaut

[expr.prim.lambda/21]

La fermeture type associé avec une lambda-expression n'a pas de défaut constructeur et un supprimée copie opérateur d'affectation. Il a un défaut constructeur de copie et un défaut constructeur de déplacement ([classe.copie]). [ Remarque: Ces fonctions membres sont définis implicitement comme d'habitude, et peut donc être définie comme étant supprimés. - la note de fin ]

ou de cppreference:

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

L'histoire de la lambda constructeur étant inaccessible remonte à ses débuts proposition, trouvé ici

Dans la section 3, deuxième alinéa, et je cite:

Dans cette traduction, __some_unique_name est un nouveau nom, pas utilisé ailleurs dans le programme de manière à provoquer des conflits avec ses utiliser comme une fermeture de type. Ce nom, et le constructeur de la classe, ne doivent pas être exposés à l'utilisateur-les seules caractéristiques que l'utilisateur pouvez compter sur la fermeture type constructeur de copie (et un déménagement constructeur si cette proposition est approuvée) et l'appel de la fonction de l'opérateur. Fermeture les types n'ont pas besoin de constructeurs par défaut, la cession les opérateurs, ou tout autre moyen d'accès au-delà des appels de fonction. Il peut être utile pour la mise en œuvre d'interdire la création de classes dérivées de la fermeture des types. ...

Comme vous pouvez le voir, la proposition a même suggéré que la création de classes dérivées de la fermeture des types devraient être interdits.


1 bien sûr, vous pouvez copier-initialiser la classe de base avec a pour initialiser un objet de type B. Voir ce


Maintenant, à votre question:

Cela peut-il être utile d'une quelconque façon?

Pas dans votre forme exacte. Le votre ne seront instanciables avec l'instance a. Toutefois, si vous héritez d'un générique Rachetables de Classe comme un type lambda, il y a deux cas je pense.

  1. Créer un Foncteur qui appelle un groupe de foncteurs dans un héritage de la séquence:

    Un exemple simplifié:

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

    exemple d'utilisation:

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

    sortie

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

    Voir en Direct sur Coliru

  2. Créer un Foncteur avec une surcharge operator()(...), surchargé avec toutes les classes de base' operator()(...):

    • Nir Friedman a déjà donné un bon exemple de ce que dans sa réponse à cette question.
    • J'ai également rédigé un semblable et simplifiée exemple, tiré de Son. Voir sur Coliru
    • Jason Lucas a également démontré ses applications pratiques dans son CppCon 2014 présentation "Polymorphisme avec les Syndicats". Vous pouvez trouver le Repo ici, l'un des emplacement exact dans le code source ici (Merci Cameron DaCamara)

Un autre truc génial: Depuis le type obtenu à partir d' make_functionSequence(...) est rachetable de classe. Vous pouvez ajouter encore plus de lambda ou remboursables par anticipation à une date ultérieure.

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

8voto

Jezor Points 1229

Les Lambdas sont des objets de fonction en dessous, avec d'autres syntatic sucre. a évalue à quelque chose comme ça (avec l' MyLambda nom un nom aléatoire, tout comme lorsque vous effectuez namespace {} - nom d'espace de noms sera aléatoire):

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

Ainsi, lorsque vous héritez d'un lambda, ce que vous faites est héritant de la classe anonyme / structure.

Comme pour l'utilité, eh bien, c'est aussi utile que tout autre héritage. Vous pouvez avoir la fonctionnalité de plusieurs lambdas avec l'héritage multiple à l'un des objets, vous pouvez ajouter de nouvelles méthodes pour l'étendre. Je ne peux pas penser à une réelle demande pour le moment, mais je suis sûr qu'il ya beaucoup.

Reportez-vous à cette question pour plus d'informations.

6voto

Nir Friedman Points 3165

Cela peut effectivement être très utile, mais cela dépend de comment direct vous voulez être sujet de la chose entière. Considérons le code suivant:

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

L'extrait de code dans votre question n'apparaît pas dans la forme exacte de n'importe où. Mais vous pouvez voir que les types de lambdas sont présumées en apply_visitor_inline. Une classe est ensuite instancié qui hérite de l'ensemble de ces lambdas. Le but? Nous sommes en mesure de combiner plusieurs lambdas en un seul, dans le but de les choses comme apply_visitor. Cette fonction s'attend à recevoir une seule fonction de l'objet que définit plusieurs operator() et de les distinguer entre eux en fonction de la surcharge. Mais il est parfois plus commode de définir un lambda qui opère sur chacun des types que nous avons à couvrir. Dans ce cas, l'héritage de lambdas fournit un mécanisme pour la combinaison.

J'ai eu le inline visiteur idée de partir d'ici: https://github.com/exclipy/inline_variant_visitor, si je n'ai pas de look à la mise en œuvre il y a, donc, cette mise en œuvre est le mien (mais je suppose que c'est très similaire).

Edit: la posté code a travaillé seulement en raison d'un bogue dans clang. Conformément à cette question (Surchargé lambdas en C++ et les différences entre clang et gcc), la recherche de multiples operator() dans les classes de base est ambigu, et, en effet, le code que j'ai posté à l'origine n'a pas de compilation de gcc. Le nouveau code compile à la fois et doit être conforme. Malheureusement, il n'existe aucun moyen apparemment de faire un variadic à l'aide de déclaration, de sorte que la récursivité doit être utilisé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X