Виконати функцію всередині шаблону функції лише для тих типів, у яких визначена функція


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

Не пов’язане з вашою проблемою, але type_infoструктура має оператора порівняння рівності , тому typeid(T) == typeid(X)має працювати також.
Якийсь програміст чувак

5
Використання: if constexprз умовою is_same_v<T,X>.
rafix07

Рішення цього офіційно стане більш елегантним пізніше цього року з Concepts. Я не дуже корисний зараз, я знаю.
січня

Існує багато способів вирішити вашу проблему. Пара, згадана вище. Ви також можете використовувати ознаки різних варіантів, щоб дізнатись, чи є у типу getIntчлен, який може викликати . Тут лише на сайті stackoverflow.com має бути досить багато питань про те, як перевірити, чи має структура чи клас певну функцію члена, якщо ви просто пошукаєте трохи.
Якийсь програміст чувак

Відповіді:


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

не обов'язково писати дві перевантаження (замінити "потрібно" на "можна" було б набагато краще imho)
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 або диспетчеризацію тегів.


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в майбутньому було більше подібних типів з членом , це не така вдала практика. Напевно, ви хочете зазначити, що
NutCracker

@NutCracker зробив так.
jrok
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.