Выполнять функцию внутри шаблона функции только для тех типов, для которых определена функция

13

У меня есть шаблон функции, который принимает много разных типов в качестве входных данных. Из этих типов только один из них имеет 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);
}
Sumit
источник
Не имеет отношения к вашей проблеме, но в type_infoструктуре есть оператор сравнения на равенство , поэтому он также typeid(T) == typeid(X)должен работать.
Какой-то программист чувак
5
Использование: if constexprс условием is_same_v<T,X>.
rafix07
Решение этой проблемы официально станет более элегантным позже в этом году с Concepts. Не супер полезно сейчас, я знаю.
между
Есть много способов решить вашу проблему. Пара, упомянутая выше. Вы также можете использовать черты разных вариантов, чтобы увидеть, имеет ли тип вызываемый getIntчлен. Здесь, на одном только stackoverflow.com, должно быть довольно много вопросов о том, как посмотреть, есть ли у структуры или класса определенная функция-член, если вы просто выполните небольшой поиск.
Какой-то программист чувак
1
связанные с stackoverflow.com/questions/257288/…
idclev 463035818

Ответы:

10

Если вы хотите иметь возможность вызывать функцию 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);
}

Проверьте это в прямом эфире .

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

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

А вот и версия C ++ 11 в прямом эфире .

Что мы имеем в 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);
}

Проверьте это в прямом эфире .

Щелкунчик
источник
До C ++ 17 эта реализация void_tвызывала проблемы у какого-то старого компилятора (как указано в ссылке).
Jarod42
Строго не нужно писать две перегрузки (лучше было бы заменить «нужно» на «может»)
idclev 463035818
@ idclev463035818 обновлен. Спасибо
NutCracker
1
@SSAnne обновлено
NutCracker
1
Определение понятия не является точным. Вы присваиваете результат для int, так что концепция должна бытьtemplate<typename T> concept HasGetInt = requires (T& v) { {v.getInt()} -> std::convertible_to<int>; };
Хуэй
8

Вы можете использовать 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 или диспетчеризацию тегов.

Jarod42
источник
if constexprэто функция C ++ 17
Андрей Семашев
Тем не менее, это будет работать только для классаX
NutCracker
Вопрос теперь обновляется только до C ++ 11 / C ++ 14
NutCracker
@NutCracker: Не очень приятно обновлять тег / вопрос и, таким образом, делать недействительными существующие ответы ... (даже если предупреждение об этом нормально).
Jarod42
я только что обновил тег ... название вопроса было обновлено OP
NutCracker
7

Сохраняйте это простым и перегружайте. Работал с 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
}

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

Джрок
источник
1
В этом случае это будет работать, поскольку существует только одна перегрузка (для X), но, если getIntв будущем будет больше подобных типов с member , это не очень хорошая практика. Вы, вероятно, хотите отметить, что
NutCracker
@NutCracker так и сделал.
Джрок