web-dev-qa-db-fra.com

Surcharger une fonction lambda

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.

14
snoopy

Non, vous ne pouvez pas surcharger le lambda!

Les lambdas sont foncteurs anonymes (c'est-à-dire les objets de fonction sans nom), et non les 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, dans c ++ 17 nous avons if constexpr par 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 variabe lambda. ou
  • Un lambda générique et trouvez le type du paramètre en utilisant decltype pour le if constexpr vérifier. (crédits @ NathanOliver)

En utilisant 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 c ++ 14 ), 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]));
10
JeJo

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 une operator() surchargée. 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;
}
6
idclev 463035818

Les règles de surcharge des noms ne s'appliquent donc qu'à certains types de recherche de noms de fonctions (à la fois 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.

Désormais, vous pouvez obtenir une résolution de surcharge pour travailler avec les objets fonction, mais uniquement dans le cadre d'un seul objet. Et puis s'il y a plus d'une operator(), la résolution de surcharge peut choisir entre elles.

Un lambda, cependant, n'a aucun moyen évident d'avoir plus d'une operator(). Nous pouvons écrire une classe utilitaire simple (in c ++ 17 ) 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 de overloaded est possible à la fois c ++ 14 et c ++ 11 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.