Exécuter la fonction à l'intérieur du modèle de fonction uniquement pour les types ayant la fonction définie

13

J'ai un modèle de fonction qui prend de nombreux types différents en entrée. Parmi ces types, un seul a une getInt()fonction. Par conséquent, je veux que le code exécute la fonction uniquement pour ce type. Veuillez suggérer une solution. Merci

#include <type_traits>
#include <typeinfo>

class X {
    public:
    int getInt(){
        return 9;
    }
};

class Y{

};

template<typename T>
void f(T& v){
    // error: 'class Y' has no member named 'getInt'
    // also tried std::is_same<T, X>::value 
    if(typeid(T).name() == typeid(X).name()){
        int i = v.getInt();// I want this to be called for X only
    }
}

int main(){
    Y y;
    f(y);
}
Sumit
la source
Sans rapport avec votre problème, mais la type_infostructure a un opérateur de comparaison d'égalité , donc typeid(T) == typeid(X)devrait également fonctionner.
Un programmeur mec le
5
Utilisation: if constexpravec condition is_same_v<T,X>.
rafix07
La solution à cela deviendra officiellement plus élégante plus tard cette année avec Concepts. Pas super utile en ce moment, je sais.
sweenish
Il existe de nombreuses façons de résoudre votre problème. Un couple mentionné ci-dessus. Vous pouvez également utiliser des traits de différentes variantes pour voir si un type a un getIntmembre appelable . Il doit y avoir pas mal de questions ici sur stackoverflow.com uniquement sur la façon de voir si une structure ou une classe a une fonction membre spécifique, si vous recherchez un peu.
Un programmeur du
1
connexes stackoverflow.com/questions/257288/...
idclev 463035818

Réponses:

10

Si vous voulez pouvoir appeler une fonction fpour tous les types qui ont un membre de fonction getInt, pas seulement X, vous pouvez déclarer 2 surcharges pour la fonction f:

  1. pour les types qui ont une getIntfonction membre, y compris la classeX

  2. pour tous les autres types, y compris la classe Y.

Solution C ++ 11 / C ++ 17

Cela dit, vous pouvez faire quelque chose comme ceci:

#include <iostream>
#include <type_traits>

template <typename, typename = void>
struct has_getInt : std::false_type {};

template <typename T>
struct has_getInt<T, std::void_t<decltype(((T*)nullptr)->getInt())>> : std::is_convertible<decltype(((T*)nullptr)->getInt()), int>
{};

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T,
          typename std::enable_if<!has_getInt<T>::value, T>::type* = nullptr>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <typename T,
          typename std::enable_if<has_getInt<T>::value, T>::type* = nullptr>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Découvrez-le en direct .

Veuillez noter que std::void_tc'est introduit dans C ++ 17, mais si vous êtes limité à C ++ 11, il est vraiment facile à implémenter void_tpar vous-même:

template <typename...>
using void_t = void;

Et voici la version C ++ 11 en direct .

Qu'avons-nous en C ++ 20?

C ++ 20 apporte beaucoup de bonnes choses et l'une d'elles est les concepts . Ce qui est valable ci-dessus pour C ++ 11 / C ++ 14 / C ++ 17 peut être considérablement réduit en C ++ 20:

#include <iostream>
#include <concepts>

template<typename T>
concept HasGetInt = requires (T& v) { { v.getInt() } -> std::convertible_to<int>; };

class X {
public:
    int getInt(){
        return 9;
    }
};

class Y {};

template <typename T>
void f(T& v) {
    // only for Y
    std::cout << "Y" << std::endl;
}

template <HasGetInt T>
void f(T& v){
    // only for X
    int i = v.getInt();
    std::cout << "X" << std::endl;
}

int main() {
    X x;
    f(x);

    Y y;
    f(y);
}

Découvrez-le en direct .

Casse Noisette
la source
Avant C ++ 17, cette implémentation void_tcause des problèmes à un ancien compilateur (comme indiqué par le lien).
Jarod42
il n'est pas strictement nécessaire d'écrire deux surcharges (remplacer "besoin" par "peut" serait beaucoup mieux à mon humble avis)
idclev 463035818
@ idclev463035818 mis à jour. Merci
NutCracker
1
@SSAnne mise à jour
NutCracker
1
La définition du concept n'est pas exacte. vous attribuez le résultat à un entier, donc le concept devrait êtretemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Vous pouvez utiliser à if constexprpartir de C ++ 17:

template<typename T>
void f(T& v){
    if constexpr(std::is_same_v<T, X>) { // Or better create trait has_getInt
        int i = v.getInt();// I want this to be called for X only
    }
    // ...
}

Avant, vous devrez utiliser des surcharges et SFINAE ou la répartition des balises.

Jarod42
la source
if constexprest une fonctionnalité C ++ 17.
Andrey Semashev
Cependant, cela ne fonctionnerait que pour la classeX
NutCracker
La question est maintenant mise à jour en C ++ 11 / C ++ 14 seulement
NutCracker
@NutCracker: Pas agréable de mettre à jour la balise / question et donc d'invalider les réponses existantes ... (même si l'avertissement à ce sujet est correct).
Jarod42
je viens de mettre à jour le tag ... le titre de la question a été mis à jour par OP
NutCracker le
7

Restez simple et surchargé. A travaillé depuis au moins C ++ 98 ...

template<typename T>
void f(T& v)
{
    // do whatever
}

void f(X& v)
{
    int result = v.getInt();
}

Cela suffit s'il n'y a qu'un seul type avec getIntfonction. S'il y a plus, ce n'est plus si simple. Il y a plusieurs façons de le faire, en voici une:

struct PriorityA { };
struct PriorityB : PriorityA { };

template<typename T>
void f_impl(T& t, PriorityA)
{
    // generic version
}

// use expression SFINAE (-> decltype part)
// to enable/disable this overload
template<typename T>
auto f_impl(T& t, PriorityB) -> decltype(t.getInt(), void())
{
    t.getInt();
}

template<typename T>
void f(T& t)
{
    f_impl(t, PriorityB{ } ); // this will select PriorityB overload if it exists in overload set
                              // otherwise PriorityB gets sliced to PriorityA and calls generic version
}

Exemple en direct avec sortie de diagnostic.

jrok
la source
1
Dans ce cas, cela fonctionnerait car il n'y a qu'une seule surcharge (pour X), mais, s'il y avait plus de types similaires avec un membre getIntà l'avenir, ce n'est pas une bonne pratique. Vous voulez probablement noter que
NutCracker
@NutCracker l'a fait.
jrok