69 votes

Lambda fonctionne comme classe de base

Jouer avec les Lambdas, j'ai trouvé un comportement intéressant que je ne suis pas entièrement comprendre.

Suposé j'ai un struct Overload qui dérive de 2 paramètres du modèle, et a un using F1::operator(); de la clause.

Maintenant, si je dérivent de deux foncteurs je n'ai accès qu'à l'opérateur() de la F1 (je l'espère)

Si je dérivent de deux Fonctions Lambda ce n'est plus vrai: je peux accéder à l'opérateur() à partir de F2.

#include <iostream>

// I compiled with g++ (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8)
//
// g++ -Wall -std=c++11 -g main.cc
// g++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// or clang clang version 3.3 (tags/RELEASE_33/rc2)
// 
// clang++ -Wall -std=c++11 -g main.cc
// clang++ -Wall -std=c++11 -DFUNCTOR -g main.cc
// 
// on a Linux localhost.localdomain 3.9.6-200.fc18.i686 #1 SMP Thu Jun 13 
// 19:29:40 UTC 2013 i686 i686 i386 GNU/Linux box


struct Functor1
{
    void operator()() { std::cout << "Functor1::operator()()\n"; }
};

struct Functor2
{
    void operator()(int) { std::cout << "Functor2::operator()(int)\n"; }
};

template <typename F1, typename F2>
struct Overload : public F1, public F2
{
    Overload()
        : F1()
        , F2() {}

    Overload(F1 x1, F2 x2)
        : F1(x1)
        , F2(x2) {}

    using F1::operator(); 
};

template <typename F1, typename F2>
auto get(F1 x1, F2 x2) -> Overload<F1, F2>
{
   return Overload<F1, F2>(x1, x2);
}


int main(int argc, char *argv[])
{
    auto f = get(Functor1(), Functor2());

    f();
#ifdef FUNCTOR
    f(2); // this one doesn't work IMHO correctly
#endif

    auto f1 = get(
                  []() { std::cout << "lambda1::operator()()\n"; },
                  [](int) { std::cout << "lambda2::operator()(int)\n"; }
                  );
    f1();
    f1(2); // this one works but I don't know why


  return 0;
}

La norme précise que:

Le type de la lambda-expression (qui est aussi le type de la fermeture de l'objet) est un unique, sans nom non - union type de classe

Ainsi, chaque Lambda types doit être unique.

Je ne peux pas expliquer pourquoi il en est ainsi: tout le monde peut faire la lumière sur cette s'il vous plaît?

35voto

Jerry Coffin Points 237758

En plus de operator() , la classe définie par un lambda peut (dans les bonnes circonstances) fournir une conversion en un pointeur vers une fonction. La circonstance (ou du moins la principale) est que le lambda ne peut rien capturer.

Si vous ajoutez une capture:

 auto f1 = get(
              []() { std::cout << "lambda1::operator()()\n"; },
              [i](int) { std::cout << "lambda2::operator()(int)\n"; }
              );
f1();
f1(2);
 

... la conversion en pointer to function n'est plus fournie, aussi essayer de compiler le code ci-dessus donne l'erreur que vous attendiez probablement depuis le début:

 trash9.cpp: In function 'int main(int, char**)':
trash9.cpp:49:9: error: no match for call to '(Overload<main(int, char**)::<lambda()>, main(int, char**)::<lambda(int)> >) (int)'
trash9.cpp:14:8: note: candidate is:
trash9.cpp:45:23: note: main(int, char**)::<lambda()>
trash9.cpp:45:23: note:   candidate expects 0 arguments, 1 provided
 

14voto

sehe Points 123151

Un lambda génère un foncteur de la classe.

En effet, vous pouvez dériver à partir des lambdas et ont polymorphe lambdas!

#include <string>
#include <iostream>

int main()
{
    auto overload = make_overload(
        [](int i)          { return '[' + std::to_string(i) + ']'; },
        [](std::string s)  { return '[' + s + ']'; },
        []                 { return "[void]"; }
        );

    std::cout << overload(42)              << "\n";
    std::cout << overload("yay for c++11") << "\n";
    std::cout << overload()                << "\n";
}

Imprime

[42]
[yay for c++11]
[void]

Comment?

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)
{
    return { std::forward<Fs>(fs)... };
}

Bien sûr... ce cache toujours la magie. C'est l' Overload classe que "comme par magie" vient de tous les lambdas et expose la correspondante operator():

#include <functional>

template <typename... Fs> struct Overload;

template <typename F> struct Overload<F> {
    Overload(F&& f) : _f(std::forward<F>(f)) { }

    template <typename... Args>
    auto operator()(Args&&... args) const 
    -> decltype(std::declval<F>()(std::forward<Args>(args)...)) {
        return _f(std::forward<Args>(args)...);
    }

  private:
    F _f;
};

template <typename F, typename... Fs>
   struct Overload<F, Fs...> : Overload<F>, Overload<Fs...>
{
    using Overload<F>::operator();
    using Overload<Fs...>::operator();

    Overload(F&& f, Fs&&... fs) :  
        Overload<F>(std::forward<F>(f)),
        Overload<Fs...>(std::forward<Fs>(fs)...)
    {
    }
};

template <typename... Fs>
   Overload<Fs...> make_overload(Fs&&... fs)
{
    return { std::forward<Fs>(fs)... };
}

Voir en Direct sur Coliru

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