Lambda du C++11 et Concept

Introduction

Ce petit article va reprendre l’approche programmation par concept que j’ai expliquée partiellement dans un article précédent et l’appliquer aux lambda que proposent le C++11.

La première partie va mettre en évidence le problème, cette partie utilisera boost.range et boost.fusion. La seconde va mettre en avant une solution s’appuyant sur la notion de foncteur polymorphique et boost.phoenix. L’analyse que je propose ici est personnelle, toute critique constructive est la bienvenue.

Les lambda du C++11

Reprenons la classe Box développée dans les autres articles. Supposons cette implémentation :

#include<vector>
#include<boost/range/algoritm/for_each.hpp>

namespace br = boost::range;

template<class Data>
class Box {
    std::vector<Data> datas;

public:
    void pop_up() const {
        br::for_each(
            datas,
            [](const Data& data)
                { data.pop_up(); }
        );
    }
};

On a donc un service pop_up qui permet de faire surgir chaque élément de la boîte. Notons ici que Data respecte le concept de pop_up :

//T respecte le concept de pop_up si
T t;
t.pop_up(); //Est valide

On voudrait maintenant modifier notre classe pour décorer les objets qui surgissent, pour ça boost.range a la notion d’adaptateur : elle permet d’obtenir un range basé sur un autre ayant des propriétés différentes. Pour notre besoin, supposons que l’on dispose d’un adaptateur scary_face qui permet de donner à chaque objet un aspect effrayant, le concept de pop_up étant conservé.

void pop_up() const {
    br::for_each(
        datas | scary_face(),
        [](const /*WhatType*/& scary)
            { scary.pop_up(); }
    );
}

Quoi indiquer pour WhatType ? Dans cette situation il est possible d’associer à la lambda une méta-fonction qui va associer à std::vector le type des objets effrayants, cependant implémenter cette méta-fonction est lourd et redondant alors que le compilateur est capable de déterminer seul à la compilation le type utilisé.

Un autre problème de cette méta-fonction est son application : le calcul n’est fait qu’une et unique fois, l’ensemble des éléments sur lequel est appliquée la lambda doivent être du même type1. Si dans le cas des conteneurs standards ceci n’est pas problématique, ce n’est pas le cas de boost.fusion qui nous offre des conteneurs hétérogènes : ce qui est alors problématique.

#include<boost/fusion/algorithm/iteration/for_each.hpp>
#include<boost/fusion/container/vector.hpp>

namespace bf = boost::fusion;

template<class Data>
class Box {
    bf::vector<Data> datas;

public:
    void pop_up() const {
        bf::for_each(
            datas,
            [](const /*WhatType*/& data)
                { data.pop_up(); }
        );
    }
};

Ici il est impossible de déterminer un type convenable, même si chaque type du paquet Data respecte le concept de pop_up. On touche ici à la limite des lambda du C++11 : elles sont monomorphiques.

Foncteurs polymorphiques

Tout d’abord notons que le problème existe que les services utilisés dans la lambda soient membres ou libres, la suite de cet article suppose que l’on utilise uniquement des services libres2.

On va illustrer une solution qui n’est pas envisageable en production mais est totalement fonctionnelle dans le cadre de notre article. L’idée est de se baser sur boost.phoenix, cependant pour l’utiliser aussi simplement que les lambda du C++11 il demande d’écrire les services de manière différente. Par exemple notre service pop_up3 doit être agrémenté de :

#include<utility>
#include<boost/phoenix/core.hpp>
#include<boost/phoenix/function.hpp>

namespace bp = boost::phoenix;

struct PopUpType {
    template<class>
    struct result
    { typedef void type; };

    template<class... Arg>
    void operator()(Arg&&... arg) const
    { pop_up(std::forward(arg)...); }
};

bp::function const pop_up;

//Utilisation directe (hors lambda)
pop_up(0)();

L’utilisation requiert un parenthèsage supplémentaire. Ainsi l’on peut réécrire notre lambda :

#include<boost/fusion/algorithm/iteration/for_each.hpp>
#include<boost/phoenix/core/argument.hpp>

namespace bf = boost::fusion;
namespace bp = boost::phoenix;

void pop_up() const {
    using bp::placeholders::arg1;

    bf::for_each(
        datas | scary_face(),
        pop_up(arg1)
    );
}

On achève bien l’objectif recherché. Les restrictions qu’on a imposées sur le cadre d’application sont liées à la manière d’obtenir ce comportement.

Conclusion

L’écriture de service en C++ se base sur divers éléments :

  • Les namespaces et l’ADL
  • Les fonctions membres et libres
  • La déduction des paramètres templates

D’un coté on dispose des lambda de boost dont l’élément qui manque est les fonctions membres et d’un autre ceux du C++11 dont l’élément qui manque est la déduction des paramètres template. Ils présentent donc les deux des faiblesses, cependant la faiblesse de ceux du C++11 est plus fondamentale que celle de ceux de boost : la déduction des paramètres template est fondamentale dans l’approche de la programmation par concept alors que les fonctions membres sont un élément assez secondaires dont l’importance est principalement liée à des conventions d’écriture.

Le C++14 devrait proposer des lambda polymorphiques, et ainsi l’ensemble des éléments et les rendre donc plus acceptables pour l’utilisation dans une approche programmation par concept. En attendant le C++14, les lambda du C++11 permettent quand même d’utiliser plus aisément l’ensemble des éléments basés sur la notion de foncteur, et boost.phoenix reste utilisable lorsque les lambda du C++11 font réellement défaut.

Edit: Merci à germinolegrand pour les corrections orthographiques et gbdivers pour les conseils de mise en forme.


  1. J’exclus l’héritage de l’article 
  2. Les services membres introduisent un problème supplémentaire qui n’apporte rien pour l’introduction des foncteurs polymorphiques et serait résolu automatiquement si les lambdas du C++11 étaient polymorphiques. 
  3. Maintenant service libre. 
Publicités

Une réflexion au sujet de « Lambda du C++11 et Concept »

  1. Ping : Lambda polymorphique du C++14 | C++ | Boost — Développement

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s