Wykonaj funkcję w szablonie funkcji tylko dla tych typów, dla których zdefiniowano funkcję

13

Mam szablon funkcji, który pobiera wiele różnych typów podczas wprowadzania. Spośród tych typów tylko jeden z nich ma getInt()funkcję. Dlatego chcę, aby kod uruchamiał funkcję tylko dla tego typu. Proszę zaproponować rozwiązanie. Dzięki

#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
źródło
Nie ma związku z twoim problemem, ale type_infostruktura ma operator porównania równości , więc też typeid(T) == typeid(X)powinien działać.
Jakiś programista koleś
5
Zastosowanie: if constexprz warunkiem is_same_v<T,X>.
rafix07
Rozwiązanie to oficjalnie stanie się bardziej eleganckie jeszcze w tym roku dzięki Concepts. Wiem, że nie jest super pomocny.
sweenish
Istnieje wiele sposobów rozwiązania problemu. Para wspomniana powyżej. Możesz także użyć cech różnych wariantów, aby sprawdzić, czy dany typ ma element na żądanie getInt. Na stackoverflow.com musi być sporo pytań o to, jak sprawdzić, czy struktura lub klasa ma określoną funkcję członka, jeśli tylko trochę przeszukasz.
Jakiś programista koleś
1
powiązane stackoverflow.com/questions/257288/…
idclev 463035818

Odpowiedzi:

10

Jeśli chcesz mieć możliwość wywołania funkcji fdla wszystkich typów, które mają element członkowski funkcji getInt, nie tylko X, możesz zadeklarować 2 przeciążenia funkcji f:

  1. dla typów, które mają getIntfunkcję członka, w tym klasęX

  2. dla wszystkich innych typów, w tym klasy Y.

Rozwiązanie C ++ 11 / C ++ 17

Mając to na uwadze, możesz zrobić coś takiego:

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

Sprawdź to na żywo .

Należy pamiętać, że std::void_tzostał wprowadzony w C ++ 17, ale jeśli jesteś ograniczony do C ++ 11, to naprawdę jest bardzo łatwe do samodzielnego wdrożenia void_t:

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

A oto wersja C ++ 11 na żywo .

Co mamy w C ++ 20?

C ++ 20 przynosi wiele dobrych rzeczy, a jedną z nich są koncepcje . Przede wszystkim, co jest ważne dla C ++ 11 / C ++ 14 / C ++ 17, można znacznie zmniejszyć w 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);
}

Sprawdź to na żywo .

Orzechówka
źródło
Przed C ++ 17 ta implementacja void_tpowoduje problemy w starym kompilatorze (jak wskazuje link).
Jarod42
napisanie dwóch przeciążeń nie jest absolutnie konieczne (zastąpienie „potrzeby”
słowem
@ idclev463035818 zaktualizowano. Dzięki
NutCracker
1
Aktualizacja @SSAnne
NutCracker
1
Definicja pojęcia nie jest dokładna. przypisujesz wynik do int, więc koncepcja powinna byćtemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Hui
8

Możesz użyć if constexprz 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
    }
    // ...
}

Wcześniej będziesz musiał używać przeciążeń i SFINAE lub wysyłania tagów.

Jarod42
źródło
if constexprjest funkcją C ++ 17.
Andrey Semashev
X
Działa
Pytanie zostało zaktualizowane do wersji C ++ 11 / C ++ 14
NutCracker
@NutCracker: Nie jest przyjemne aktualizowanie tagu / pytania i unieważnianie istniejących odpowiedzi ... (nawet jeśli ostrzeżenie o tym jest w porządku).
Jarod42
Właśnie zaktualizowałem tag ... tytuł pytania został zaktualizowany przez OP
NutCracker
7

Prostota i przeciążenie. Pracuje od co najmniej C ++ 98 ...

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

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

To wystarczy, jeśli istnieje tylko jeden typ z getIntfunkcją. Jeśli jest więcej, nie jest to już takie proste. Jest na to kilka sposobów, oto jeden:

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
}

Przykład na żywo z wyjściem diagnostycznym.

żart
źródło
1
W tym przypadku działałoby to, ponieważ istnieje tylko jedno przeciążenie (dla X), ale jeśli getIntw przyszłości będzie więcej podobnych typów z elementem, nie jest to taka dobra praktyka. Prawdopodobnie chcesz to zauważyć
NutCracker
@NutCracker Zrobił to.
jrok