web-dev-qa-db-fra.com

Comment créer un lambda générique variadique?

Depuis C++ 14, nous pouvons utiliser des lambdas génériques:

auto generic_lambda = [] (auto param) {};

Cela signifie essentiellement que son opérateur d'appel est basé sur des modèles basés sur les paramètres marqués comme auto.

La question est de savoir comment créer un lambda qui peut accepter un nombre variadique de paramètres de la même manière qu'un modèle de fonction variadique fonctionnerait? Si ce n'est pas possible, quelle est la chose la plus proche qui pourrait être utilisée de la même manière? Comment le stockeriez-vous? Est-il possible dans un std::function?

36
Drax

Syntaxe

Comment créer un lambda générique variadique?

Vous pouvez créer un lambda générique variadique avec la syntaxe suivante:

auto variadic_generic_lambda = [] (auto... param) {};

Fondamentalement, vous ajoutez simplement ... entre auto (éventuellement qualifié de référence) et le nom de votre pack de paramètres.

Donc, généralement, l'utilisation de références universelles donnerait:

auto variadic_generic_lambda = [] (auto&&... param) {};

Usage

Comment utilisez-vous les paramètres?

Vous devez considérer le paramètre générique variadic comme ayant un type de pack de paramètres de modèle, car c'est le cas. Cela implique plus ou moins que la plupart sinon la totalité de l'utilisation de ces paramètres nécessitera des modèles d'une manière ou d'une autre.

Voici un exemple typique:

#include <iostream>

void print(void)
{
}

template <typename First, typename ...Rest>
void print(const First& first, Rest&&... Args)
{
  std::cout << first << std::endl;
  print(Args...);
}

int     main(void)
{
  auto variadic_generic_lambda = [] (auto... param)
    {
      print(param...);
    };

  variadic_generic_lambda(42, "lol", 4.3);
}

Espace de rangement

Comment stockez-vous un lambda générique variadique?

Vous pouvez soit utiliser auto pour stocker un lambda dans une variable de son propre type, soit le stocker dans un std::function mais vous ne pourrez l'appeler qu'avec la signature fixe que vous avez donnée à ce std::function:

auto variadic_generic_lambda = [] (auto... param) {};

std::function<void(int, int)> func = variadic_generic_lambda;

func(42, 42); // Compiles

func("lol"); // Doesn't compile

Qu'en est-il des collections de lambdas génériques variadiques?

Étant donné que chaque lambda a un type différent, vous ne pouvez pas stocker leur type direct dans les conteneurs homogènes habituels de la STL. La manière de procéder avec les lambdas non génériques consiste à les stocker dans un std::function qui aura un appel de signature fixe et qui ne restreindra rien puisque votre lambda n'est pas générique en premier lieu et ne peut être invoqué que de cette façon:

auto non_generic_lambda_1 = [] (int, char) {};
auto non_generic_lambda_2 = [] (int, char) {};

std::vector<std::function<void(int, char)>> vec;

vec.Push_back(non_generic_lambda_1);
vec.Push_back(non_generic_lambda_2);

Comme expliqué dans la première partie de cette section de stockage si vous pouvez vous limiter à une signature d'appel fixe donnée, vous pouvez faire de même avec des lambdas génériques variadiques.

Si vous ne pouvez pas, vous aurez besoin d'une forme de récipient hétérogène comme:

  • std::vector<boost::variant>
  • std::vector<boost::any>
  • boost::fusion::vector

Voir cette question pour un exemple de conteneur hétérogène.

Quoi d'autre ?

Pour des informations plus générales sur les lambdas et pour plus de détails sur les membres générés et comment utiliser les paramètres dans le lambda, voir:

30
Drax

Je ne sais pas quelle est votre intention, mais au lieu de la stocker dans un std::function vous pouvez utiliser le lambda lui-même pour capturer les paramètres. Ceci est un exemple discuté sur la liste de diffusion boost. Il est utilisé dans l'implémentation boost :: hana

auto list = [](auto ...xs) {
    return [=](auto access) { return access(xs...); };
};

auto head = [](auto xs) {
    return xs([](auto first, auto ...rest) { return first; });
};

auto tail = [](auto xs) {
    return xs([](auto first, auto ...rest) { return list(rest...); });
};

auto length = [](auto xs) {
    return xs([](auto ...z) { return sizeof...(z); });
};

// etc...
// then use it like

auto three = length(list(1, '2', "3")); 
40
mkaes

Considère ceci

#include <iostream>

    namespace {

        auto out_ = [] ( const auto & val_) 
        {
            std::cout << val_;
            return out_ ;
        };

        auto print = [](auto first_param, auto... params)
        {
            out_(first_param);

            // if there are  more params
            if constexpr (sizeof...(params) > 0) {
                // recurse
                print(params...);
            }
                return print;
        };
    }

int main()
{
    print("Hello ")("from ")("GCC ")(__VERSION__)(" !"); 
}

( wandbox here ) Cette lambda "print" est:

  • Variadic
  • Récursif
  • Générique
  • Vite

Et aucun modèle en vue. (juste en dessous :)) Pas de code C++ qui ressemble à du bruit radio. Simple, propre et surtout:

  • Facile à maintenir

Pas étonnant que "cela ressemble à une nouvelle langue".

2
user5560811