Führen Sie die Funktion innerhalb der Funktionsvorlage nur für die Typen aus, für die die Funktion definiert ist

13

Ich habe eine Funktionsvorlage, die viele verschiedene Typen als Eingabe verwendet. Von diesen Typen hat nur einer eine getInt()Funktion. Daher möchte ich, dass der Code die Funktion nur für diesen Typ ausführt. Bitte schlagen Sie eine Lösung vor. Vielen Dank

#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
quelle
Bezieht sich nicht auf Ihr Problem, aber die type_infoStruktur verfügt über einen Gleichheitsvergleichsoperator und typeid(T) == typeid(X)sollte daher auch funktionieren.
Ein Programmierer
5
Verwendung: if constexprmit Bedingung is_same_v<T,X>.
rafix07
Die Lösung dafür wird später in diesem Jahr mit Concepts offiziell eleganter. Im Moment nicht besonders hilfreich, ich weiß.
Zwischen
Es gibt viele Möglichkeiten, Ihr Problem zu lösen. Ein paar oben erwähnt. Sie könnten auch verwenden Merkmale verschiedener Varianten verwenden, um festzustellen, ob ein Typ ein aufrufbares getIntMitglied hat. Allein hier auf stackoverflow.com muss es einige Fragen geben, wie Sie feststellen können, ob eine Struktur oder Klasse eine bestimmte Elementfunktion hat, wenn Sie nur ein wenig suchen.
Ein Programmierer
1
verwandte stackoverflow.com/questions/257288/…
idclev 463035818

Antworten:

10

Wenn Sie in der Lage sein möchten, eine Funktion ffür alle Typen mit Funktionselement aufzurufen , können Sie getIntnicht nur X2 Überladungen für die Funktion deklarieren f:

  1. für Typen mit getIntElementfunktion, einschließlich KlasseX

  2. für alle anderen Typen, einschließlich Klasse Y.

C ++ 11 / C ++ 17-Lösung

In diesem Sinne könnten Sie so etwas tun:

#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);
}

Probieren Sie es live aus .

Bitte beachten Sie, dass dies std::void_tin C ++ 17 eingeführt wird. Wenn Sie jedoch auf C ++ 11 beschränkt sind, ist die Implementierung void_tauf eigene Faust sehr einfach :

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

Und hier ist C ++ 11 Version live .

Was haben wir in C ++ 20?

C ++ 20 bringt viele gute Dinge und eines davon sind Konzepte . Das, was für C ++ 11 / C ++ 14 / C ++ 17 gültig ist, kann in C ++ 20 erheblich reduziert werden:

#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);
}

Probieren Sie es live aus .

Nussknacker
quelle
Vor C ++ 17 verursacht diese Implementierung jedoch void_tProbleme bei einigen alten Compilern (wie durch den Link angegeben).
Jarod42
Es ist nicht unbedingt notwendig, zwei Überladungen zu schreiben (das Ersetzen von "Need" durch "Can" wäre imho viel besser)
idclev 463035818
@ idclev463035818 aktualisiert. Danke
NutCracker
1
@SSAnne aktualisiert
NutCracker
1
Die Konzeptdefinition ist nicht korrekt. Sie weisen das Ergebnis einem int zu, so dass das Konzept sein solltetemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Sie können if constexprvon C ++ 17 verwenden:

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
    }
    // ...
}

Zuvor müssen Sie Überladungen und SFINAE- oder Tag-Dispatching verwenden.

Jarod42
quelle
if constexprist eine C ++ 17-Funktion.
Andrey Semashev
Dies würde jedoch nur für die Klasse X
funktionieren
Die Frage wurde jetzt nur auf C ++ 11 / C ++ 14
NutCracker
@NutCracker: Nicht schön, Tag / Frage zu aktualisieren und so vorhandene Antworten ungültig zu machen ... (auch wenn eine Warnung in Ordnung ist).
Jarod42
Ich habe gerade das Tag aktualisiert ... Der Titel der Frage wurde von OP
NutCracker
7

Halten Sie es einfach und überladen. Hat seit mindestens C ++ 98 funktioniert ...

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

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

Dies reicht aus, wenn es immer nur einen Typ mit getIntFunktion gibt. Wenn es mehr gibt, ist es nicht mehr so ​​einfach. Es gibt verschiedene Möglichkeiten, hier eine:

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
}

Live-Beispiel mit Diagnoseausgabe.

jrok
quelle
1
In diesem Fall würde dies funktionieren, da es nur eine Überlastung (für X) gibt. Wenn es jedoch getIntin Zukunft mehr ähnliche Typen mit Mitgliedern gibt , ist dies keine so gute Praxis. Sie möchten wahrscheinlich das beachten
NutCracker
@NutCracker Hat das getan.
Jrok