Как определить, содержит ли класс определенную функцию-член во время компиляции

Возможный дубликат:
Можно ли написать шаблон C ++ для проверки существования функции?

скажем, есть 2 класса:

struct A{ int GetInt(){ return 10; } };
struct B{ int m; };

Я хочу использовать объект типа A или B в следующей функции

tempate< typename T >
int GetInt( const T & t )
{
   //if it's A, I'll call: return t.GetInt();
   //if its' B, I'll call: return t.m;
}

Теперь, поскольку существует целая куча классов, некоторые содержат GetInt (), некоторые нет, я не хочу писать специализацию для каждого типа, я хочу различать их только по ', содержащим GetInt () или нет во время компиляции ', как мне это сделать?


person JQ.    schedule 18.10.2010    source источник
comment
Возможно, вам стоит изучить виртуальные функции и полиморфизм.   -  person JoshD    schedule 19.10.2010
comment
Если вы не собираетесь предполагать, что все ваши классы, не являющиеся GetInt экземплярами, имеют int член с именем m, я не понимаю, как вы можете расширить это без специализации для каждого. Какова реализация вашего шаблона функции по умолчанию?   -  person Steve Townsend    schedule 19.10.2010
comment
Вы не можете заставить эту функцию работать, потому что GetInt не const, а t.   -  person GManNickG    schedule 19.10.2010


Ответы (4)


Кража из здесь, и если вы исправите свой код так, чтобы GetInt было константой, мы получим:

HAS_MEM_FUNC(GetInt, has_GetInt);

template <bool B>
struct bool_type
{
    static const bool value = B;
};

typedef bool_type<true> true_type;
typedef bool_type<false> false_type;

namespace detail
{
    template <typename T>
    int get_int(const T& pX, true_type)
    {
        return pX.GetInt();
    }

    template <typename T>
    int get_int(const T& pX, false_type)
    {
        return pX.m;
    }
}

template <typename T>
int get_int(const T& pX)
{
    return detail::get_int(pX,
                            has_GetInt<T, int (T::*)() const>::value);
}

Однако это довольно ужасный дизайн. Вам следует исправить проблему, а не применять исправление.

person GManNickG    schedule 19.10.2010
comment
здорово, спасибо, что украли для меня! - person JQ.; 19.10.2010

Ошибка замены не является ошибкой или, более компактно, SFINAE

Но в вашем конкретном случае вам не нужны SFINAE, виртуальные участники или что-то подобное.

Вам просто нужна обычная перегруженная функция.

int GetInt(A& t) { return t.GetInt(); }
int GetInt(const B& t) { return t.m; }

Если есть код, который необходимо совместно использовать в разных версиях, реорганизуйте его так, чтобы был шаблон, который вызывает перегруженную встроенную функцию, все поведение, зависящее от типа, находится во встроенной функции, а все общее поведение - в шаблоне.

Для вашей потребности "У меня много, много классов" SFINAE будет выглядеть примерно так:

template<typename T>
int GetInt(const T& t, int (T::*extra)() const = &T::GetInt)
{
    return t.GetInt();
}

template<typename T>
auto GetInt(const T& t) -> decltype(t.m)
{
    return t.m;
}

РЕДАКТИРОВАТЬ: Реальность SFINAE намного уродливее, по крайней мере, до тех пор, пока не появится C ++ 0x. Фактически, он начинает выглядеть так же плохо, как и ответ GMan.

struct A{ int GetInt() const { return 10; } };
struct B{ int m; };

template<typename T, int (T::*extra)() const>
struct has_mfunc
{
    typedef int type;
};

template<typename T>
typename has_mfunc<T, &T::GetInt>::type GetInt(const T& t)
{
    return t.GetInt();
}

template<typename T, typename U, U (T::*extra)>
struct has_field
{
    typedef U type;
};

template<typename T>
typename has_field<T, int, &T::m>::type GetInt(const T& t)
{
    return t.m;
}

int main(void)
{
   A a;
   B b;
   b.m = 5;
   return GetInt(a) + GetInt(b);
}
person Ben Voigt    schedule 19.10.2010
comment
Вы имели в виду t. * Extra () вместо t.GetInt ()? Я не понимаю, как использовать дополнительный параметр - person Chubsdad; 19.10.2010
comment
@Chubsdad: дополнительный параметр присутствует исключительно для того, чтобы вызвать сбой подстановки, так что перегрузка удаляется из списка совпадений до создания тела шаблона (в это время компилятор выдает ошибку, пытаясь найти B::GetInt). - person Ben Voigt; 19.10.2010
comment
Стоит упомянуть, что decltype отсутствует в текущем Стандарте ...: - /. Кроме того, вопрос очень ясно дает понять, что я не хочу писать специализацию для каждого типа - что я бы предпринял, чтобы исключить предложение о перегрузке. - person Tony Delroy; 19.10.2010
comment
@Tony: достаточно просто использовать указатель на член для проверки t.m, просто подумал, поскольку я уже приводил пример этого, я бы тоже привел пример с decltype. - person Ben Voigt; 19.10.2010
comment
спасибо, ребята, но код не компилируется. - person JQ.; 19.10.2010
comment
@Ben: не могли бы вы рассказать, как это сделать, не делая поиск в GetInt неоднозначным? Спасибо. - person Tony Delroy; 19.10.2010
comment
@Tony: очевидно, параметры по умолчанию не разрешаются, пока не станет слишком поздно для SFINAE. С C ++ 0x, суффиксный возвращаемый тип с использованием decltype упростит эту задачу. А пока все в порядке. - person Ben Voigt; 19.10.2010
comment
+1 за то, что довелось увидеть это до конца. Так что привык к этой уродливой неразберихе, я не думал, насколько это будет проще в C ++ 0x ... спасибо за это :-). - person Tony Delroy; 19.10.2010

Технически это просто включает в себя несколько шаблонных арканов, которые вы можете найти в Google, например. has_member или тому подобное. Вне манжеты, в коде обнаружения, если бы я мог написать такое, я бы просто сделал своего рода фальшивый производный от рассматриваемого класса и проверил размер члена производного класса.

Однако не делайте этого.

Что еще делать - зависит. Но кажется, что ваши классы соответствуют двум разным «схемам», так сказать, без того, чтобы эти схемы были доступны через систему типов (например, кажется, что классы не являются производными от двух базовых классов A и B). Затем один из вариантов - ввести шаблон признаков, который сообщает оберткам, является ли параметр шаблона T схемой A или B. Специализируйте признаки для каждого соответствующего класса, который отличается от значения по умолчанию. Выберите значение по умолчанию, чтобы минимизировать объем работы.

Ура & hth.,

person Cheers and hth. - Alf    schedule 18.10.2010

Именно для этого и существует наследование. Вы можете легко использовать использование dynamic_cast для вопросов типа во время выполнения. Например, вы можете определить абстрактный базовый класс с именем HasGetInt и унаследовать от него классы, которым нужна эта функция, а не изобретать колесо.

person rerun    schedule 18.10.2010
comment
В шаблонном типе он может делать все, что захочет, пока это не будет создано с типом. Он, конечно, мог перегрузить типы шаблонных функций, но я часто нахожу, что эти типы решений лучше решаются с помощью модели интерфейса. - person rerun; 19.10.2010
comment
спасибо, ребята, но код унаследован, я стараюсь не изменять существующий код, но добавляю некоторые служебные функции поверх него, чтобы облегчить жизнь. - person JQ.; 19.10.2010