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


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.