Выполнять функцию внутри шаблона функции только для тех типов, для которых определена функция - PullRequest
13 голосов
/ 07 февраля 2020

У меня есть шаблон функции, который принимает много разных типов в качестве входных данных. Из этих типов только один из них имеет функцию getInt(). Следовательно, я хочу, чтобы код запускал функцию только для этого типа. Пожалуйста, предложите решение. Спасибо

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

Ответы [ 3 ]

10 голосов
/ 07 февраля 2020

Если вы хотите иметь возможность вызывать функцию f для всех типов, которые имеют член функции getInt, а не только X, вы можете объявить 2 перегрузки для функции f:

  1. для типов, которые имеют getInt функцию-член, включая класс X

  2. для всех других типов, включая класс Y.

C ++ 11 / C ++ 17 решение

Имея это в виду, вы можете сделать что-то вроде этого:

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

Проверьте это live .

Обратите внимание, что std::void_t введено в C ++ 17, но если вы ограничены C ++ 11, то это действительно просто реализовать void_t самостоятельно:

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

А вот C ++ 11 версия live .

Что у нас есть в C ++ 20?

C ++ 20 приносит много хороших вещей, и одна из них - концепции . Выше всего, что справедливо для C ++ 11 / C ++ 14 / C ++ 17, можно значительно сократить в 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);
}

Проверьте это live .

8 голосов
/ 07 февраля 2020

Вы можете использовать if constexpr из 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
    }
    // ...
}

Раньше вам придется использовать перегрузки и SFINAE или диспетчеризацию тегов.

7 голосов
/ 07 февраля 2020

Сохраняйте это простым и перегруженным. Работал с C ++ 98 ...

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

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

Этого достаточно, если существует только один тип с функцией getInt. Если есть больше, это уже не так просто. Есть несколько способов сделать это, вот один:

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
}

Живой пример с диагностикой c вывод.

...