Surcharger une fonction lambda

14

Comment surcharger une simple fonction lambda locale?

SSE du problème d'origine:

#include <iostream>
#include <map>

void read()
{
    static std::string line;
    std::getline(std::cin, line);

    auto translate = [](int idx)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    };

    auto translate = [](char c)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                             {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[c];
    };

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));
    std::cout << r << c << std::endl;
}

int main()
{
    read();
    return 0;
}

Les messages d'erreur

error: conflicting declaration 'auto translate'
note: previous declaration as 'read()::<lambda(int)> translate'

S'il vous plaît, ne vous occupez pas de ne pas vérifier les entrées utilisateur, il s'agit d'un SSE.

snoopy
la source
7
Les lambdas ne sont pas des fonctions, ce sont des objets, donc la surcharge ne s'applique jamais à eux. translatene sont que des variables locales qui ne peuvent pas réutiliser le même nom.
user7860670
2
related / dupe: stackoverflow.com/questions/32475576/…
NathanOliver

Réponses:

10

Non, vous ne pouvez pas surcharger le lambda!

Les lambdas sont des foncteurs anonymes (c'est-à-dire des objets de fonction sans nom) et non des fonctions simples. Par conséquent, la surcharge de ces objets n'est pas possible. Ce que vous essayez essentiellement de faire est presque

struct <some_name>
{
    int operator()(int idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

struct <some_name>
{
    int operator()(char idx) const
    {
        return {}; // some int
    }
}translate; // >>> variable name

Ce qui n'est pas possible, car le même nom de variable ne peut pas être réutilisé en C ++.


Cependant, en nous avons if constexprpar lequel on peut instancier la seule branche qui est vraie au moment de la compilation.

Cela signifie que les solutions possibles sont:

  • Un seul modèle de variabe lambda. ou
  • Un lambda générique et trouver le type du paramètre à l'aide decltype de la if constexprvérification. (Crédits @NathanOliver )

En utilisant un modèle variabe, vous pouvez faire quelque chose comme. ( Voir une démo en direct en ligne )

#include <type_traits> // std::is_same_v

template<typename T>
constexpr auto translate = [](T idx) 
{
    if constexpr (std::is_same_v<T, int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<T, char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

et l'appelle comme

int r = translate<int>(line[0]);
int c = translate<char>(line[1]);

En utilisant lambda générique (depuis ), ce qui précède sera: ( Voir une démo en direct en ligne )

#include <type_traits> // std::is_same_v

constexpr auto translate = [](auto idx) 
{
    if constexpr (std::is_same_v<decltype(idx), int>)
    {
        constexpr static int table[8]{ 7,6,5,4,3,2,1,0 };
        return table[idx];
    }
    else if constexpr (std::is_same_v<decltype(idx), char>)
    {
        std::map<char, int> table{ {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3}, {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
        return table[idx];
    }
};

et appelez le lambda comme vous le faites maintenant:

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));
JeJo
la source
3
Je trouve cela incroyable
snoopy
1
Tout d'abord, vos else ifbesoins doivent l'être else if constexpr. Deuxièmement, pourquoi utiliser un modèle de variable? Vous pourriez simplement faire le lambda générique et vos checls deviendraient if constexpr (std::is_same_v<decltype(idx), int>)etelse if constexpr (std::is_same_v<decltype(idx), char>)
NathanOliver
6

Les lambdas sont essentiellement du sucre syntaxique pour les foncteurs définis localement. Pour autant que je sache, ils n'ont jamais été destinés à être surchargés pour être appelés avec des paramètres différents. Notez que chaque expression lambda est d'un type différent, donc même l'erreur immédiate mise à part, votre code ne peut pas fonctionner comme prévu.

Vous pouvez cependant définir un foncteur avec un surchargé operator(). Ce sera exactement ce que vous obtiendriez de lambdas si c'était possible. Vous n'avez tout simplement pas la syntaxe laconique.

Quelque chose comme:

void read()
{
    static std::string line;

    struct translator {
          int operator()(int idx) { /* ... */ }
          int operator()(char x)  { /* ... */ }
    };
    translator translate;


    std::getline(std::cin, line);

    int r = translate(static_cast<int>(line[0]));
    int c = translate(static_cast<char>(line[1]));

    std::cout << r << c << std::endl;
}
idclev 463035818
la source
attendez une minute, vous appelez la syntaxe lambda nice ??
user7860670
1
@VTT c'est bien que la syntaxe soit concise. Comparé à des choses plus anciennes, ce n'est pas trop mal
idclev 463035818
5

Les règles de surcharge des noms ne s'appliquent donc qu'à certains types de recherche de noms de fonctions (libres et méthodes).

Les lambdas ne sont pas des fonctions, ce sont des objets avec un opérateur d'appel de fonction. Une surcharge ne peut donc pas se produire entre deux lambdas différents.

Maintenant, vous pouvez obtenir une résolution de surcharge pour travailler avec des objets fonction, mais uniquement dans le cadre d'un seul objet. Et puis s'il y en a plusieurs operator(), la résolution de surcharge peut choisir entre eux.

Un lambda, cependant, n'a aucun moyen évident d'en avoir plus d'un operator(). Nous pouvons écrire une classe utilitaire simple (en ) pour nous aider:

template<class...Fs>
struct overloaded : Fs... {
  using Fs::operator()...;
};

et un guide de déduction:

template<class...Fs>
overloaded(Fs...) -> overloaded<Fs...>;

avec ces deux, nous pouvons surcharger deux lambdas:

static std::string line;
std::getline(std::cin, line);

auto translate_int = [](int idx){
    constexpr static int table[8] {7,6,5,4,3,2,1,0};
    return table[idx];
};

auto translate_char = [](char c) {
    std::map<char, int> table { {'a', 0}, {'b', 1}, {'c', 2}, {'d', 3},
                                {'e', 4}, {'f', 5}, {'g', 6}, {'h', 7} };
    return table[c];
};
auto translate = overloaded{ translate_int, translate_char };

int r = translate(static_cast<int>(line[0]));
int c = translate(static_cast<char>(line[1]));

Et.. Voila.

L'écriture overloadedest possible à la fois en et mais nécessite plus de travail et est moins élégante. Une fois que vous êtes conscient du problème, trouver une solution qui correspond à ce que votre compilateur prend en charge en termes de fonctionnalités C ++ ne devrait pas être difficile.

Yakk - Adam Nevraumont
la source
Si je comprends bien, chaque lamda "surchargée" a son propre bloc de capture, c'est-à-dire que ces lambdas ne partagent rien (et perdent probablement du temps CPU à capturer les mêmes données encore et encore). Y a-t-il une chance que la norme C ++ ait quelque chose à rectifier? Ou la seule option est variadic generic lamda+ if constexprpour séparer les appels?
CM
@CM Pour poser une question sur le débordement de pile, veuillez appuyer sur le bouton [Poser une question] en haut à droite, et non sur le bouton [Ajouter un commentaire]. Merci!
Yakk - Adam Nevraumont