web-dev-qa-db-fra.com

Déduction de modèle C++ de lambda

J'ai une fonction qui prend deux std::functions en arguments. Le paramètre de la deuxième fonction a le même type que le résultat de la première.

J'ai écrit un modèle de fonction comme ceci:

template<typename ResultType>
void examplFunction(std::function<ResultType()> func, std::function<void(ResultType)> func2) {
  auto x = func();
  func2(x);
}

Je peux l'appeler avec:

void f() {
  examplFunction<int>([]() { return 1; },   //
                      [](int v) { std::cout << "result is " << v << std::endl; });
}

Existe-t-il un moyen de supprimer le <int> à examplFunction<int> et de laisser le compilateur déduire le type de ResultType?

26
ErWu

Avez-vous réellement besoin de std::function dedans? std::function est utile lorsque vous avez besoin de type erasure . Avec les modèles, vous pouvez généralement le sauter complètement:

template<class F1, class F2>
void examplFunction(F1 func, F2 func2, decltype(func2(func()))* sfinae = nullptr) {
  auto x = func();
  func2(x);
}

Le paramètre sfinae permet de s'assurer que la fonction ne peut être appelée qu'avec des fonctions telles que func2 puisse être appelé avec le résultat de func.

25
Angew

Oui il y a.

template<typename ResultType>
void examplFunction_impl(std::function<ResultType()> func, std::function<void(ResultType)> func2) {
    auto x = func();
    func2(x);
}

template<class F1, class F2>
void examplFunction(F1&& f1, F2&& f2)
{
    using ResultType = decltype(f1());
    examplFunction_impl<ResultType>(std::forward<F1>(f1), std::forward<F2>(f2));
}

Démo

Dans ce cas, vous avez besoin que f1 soit invocable sans arguments, de sorte que vous puissiez déterminer le type de retour dans la fonction d'assistance. Ensuite, vous appelez la fonction réelle en spécifiant explicitement ce type de retour.

Vous pouvez ajouter un peu de SFINAE pour vous assurer que cette fonction ne participe à la résolution de surcharge que lorsque f1 peut effectivement être appelé ainsi (et si f2 peut également être appelé avec la valeur de retour f1).

Bien que je sois d’accord avec @Angew sur le fait que dans l’exemple donné, il n’est pas nécessaire d'utiliser std::function. Cela pourrait bien sûr être différent dans une situation réelle.

12
Max Langhof

std::function a un constructeur basé sur un modèle (et par ailleurs non contraint), donc le déduire d'un simple type d'argument n'est pas une transaction aussi simple. Si ces arguments doivent toujours être std::functions, vous pouvez ignorer un <int> pour le prix de deux std::functions et laisser les guides de déduction faire le reste:

void f() {
    examplFunction(std::function([]() { return 1; }),   //
                   std::function([](int v) { std::cout << "result is "
                                             << v << std::endl; }));
}

Un fait amusant est que cela ne fonctionne pas toujours. Par exemple, l'implémentation actuelle de libc ++ manque de guides pour std::function, violant ainsi le standard.

3
bipll

La réponse d'Angew est excellente (et devrait être la réponse acceptée), mais il manque le détail mineur de la vérification que la fonction de section ne renvoie rien. Pour ce faire, vous devez utiliser le trait de type std::is_void et std :: enable_if:

template<class F1, class F2>
void examplFunction(F1 func, F2 func2, std::enable_if_t<std::is_void_v<decltype(func2(func()))>, void*> sfinae = nullptr) {
    auto x = func();
    func2(x);
}

Cette évidence est plus verbeuse et difficile à lire si vous n'êtes pas familier avec les caractères de type et SFINAE. Ce n'est donc probablement pas la meilleure voie à suivre si vous n'avez pas besoin de vous assurer que F2 renvoie void.

0
bobshowrocks