Подтвердить что ты не робот

Можно ли использовать type_traits/SFINAE, чтобы определить, определяет ли класс член TYPE?

Я видел этот question, который позволяет проверять существование функции-члена, но я пытаюсь узнать, имеет ли класс тип члена.

В приведенном ниже примере оба параметра оцениваются как "false", но я хотел бы найти способ, чтобы has_bar<foo1>::value оценивался как false, а has_bar<foo2>::value оценивался как true.

Возможно ли это?

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

template <typename T>
class has_bar
{
    typedef char yes;
    typedef long no;

    template <typename C> static yes check( decltype(&C::bar) ) ;
    template <typename C> static no  check(...);
public:
    enum { value = sizeof(check<T>(0)) == sizeof(yes) };
};

int main()
{
    std::cout << has_bar<foo1>::value << std::endl;
    std::cout << has_bar<foo2>::value << std::endl;
    return 0;
}

Изменить: реализовать специализацию в ответ на ответы ниже:

... если вы используете C:: bar в целевом шаблоне, шаблон будет автоматически отбрасывается для типов, которые не имеют этого вложенного типа.

Я попытался сделать это, но я явно что-то пропустил

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

template <typename T, typename U = void>
struct target
{
    target()
    {
        std::cout << "default target" << std::endl;
    }
};

template<typename T>
struct target<T, typename T::bar>
{
    target()
    {
        std::cout << "specialized target" << std::endl;
    }
};

int main()
{
    target<foo1>();
    target<foo2>();
    return 0;
}
4b9b3361

Ответ 1

Вы не можете получить указатель на член для члена типа:

template <typename C> static yes check( decltype(&C::bar) ) ;

Подвыражение &C::bar будет действительным только тогда, когда bar является не-типом членом C. Но вам нужно проверить, является ли это типом. Минимальным изменением шаблона может быть:

template <typename C> static yes check( typename C::bar* ) ;

Если bar является вложенным типом C, то эта перегрузка функции будет действительным кандидатом (0 будет указателем на любой тип C::bar), но если C не содержит вложенный bar, тогда он будет отброшен, и второй тест будет единственным кандидатом.

Возникает другой вопрос относительно того, нужен ли этот признак вообще, поскольку, если вы используете C::bar в целевом шаблоне, шаблон будет автоматически отбрасываться для типов, у которых нет этого вложенного типа.


ИЗМЕНИТЬ

Что я имел в виду, так это то, что в вашем подходе вам нужно создать признак для каждого возможного вложенного типа, просто для создания шаблона, который выполняет или не имеет вложенный тип (enable_if). Возьмем другой подход... Сначала мы определяем общую утилиту для выбора типа на основе условия, это не требуется для этой проблемы, и более простой template <typename T> void_type { typedef void type; }; будет достаточно, но шаблон полезности может быть полезен в других случаях

// General utility: if_<Condition, Then, Else>::type
// Selects 'Then' or 'Else' type based on the value of 
// the 'Condition'
template <bool Condition, typename Then, typename Else = void>
struct if_ {
   typedef Then type;
};
template <typename Then, typename Else>
struct if_<false, Then, Else > {
   typedef Else type;
};

Теперь просто нужно использовать SFINAE для специализированных шаблонов шаблонов:

template <typename T, typename _ = void> 
struct target {
   // generic implementation
};

template <typename T>
struct target<T, typename if_<false,typename T::bar>::type> {
   // specialization for types holding a nested type `T::bar`
};

Обратите внимание, что основным отличием вашего подхода является использование дополнительного промежуточного шаблона (тот, для которого замещение будет сбой - и не является ошибкой), что дает тип void (при успешном завершении). Вот почему шаблон void_type выше также будет работать: вам просто нужно использовать вложенный тип в качестве аргумента для шаблона, и это не сработает, вам все равно, что делает шаблон, до тех пор, пока оценка является вложенным type (который должен быть void), если он преуспевает.

В случае, если это не очевидно (сначала это было не для меня), почему ваш подход не работает, подумайте о том, что должен делать компилятор, когда он сталкивается с target<foo2>: первым шагом является поиск того, что есть шаблон под названием target, но этот шаблон принимает два аргумента, из которых только один был предоставлен. Затем он выглядит в базовом шаблоне (тот, который не является специализированным), и обнаруживает, что второй аргумент может быть по умолчанию void. С этого момента будет рассмотрен ваш экземпляр: target<foo2,void> (после ввода аргумента по умолчанию). И он попытается соответствовать лучшей специализации. Будут рассмотрены только специализации, для которых второй аргумент void. Ваш шаблон выше сможет использовать только специализированную версию, если T::bar - void (вы можете проверить это, изменив foo2 на: struct foo2 { typedef void bar; }. Потому что вы не хотите, чтобы специализация выполнялась только тогда, когда вложенный тип void вам нужен дополнительный шаблон, который займет C::bar (и, следовательно, сбой, если тип не содержит вложенный bar), но всегда будет давать void как вложенный тип.

Ответ 2

Попробуйте это

template<class T>
struct Void {
  typedef void type;
};

template<class T, class U = void>
struct has_bar {
    enum { value = 0 };
};

template<class T>
struct has_bar<T, typename Void<typename T::bar>::type > {
    enum { value = 1 };
};

Ответ 3

Я предпочитаю обернуть его макросом.

test.h:

#include <type_traits>

template<typename ...>
struct void_type
{
     using type = void;
};

template<typename ...T>
using void_t = typename void_type<T...>::type;

#define HAS_TYPE(NAME) \
template<typename, typename = void> \
struct has_type_##NAME: std::false_type \
{}; \
template<typename T> \
struct has_type_##NAME<T, void_t<typename T::NAME>>: std::true_type \
{} \

HAS_TYPE(bar);

test.cpp:

#include <iostream>

struct foo1;
struct foo2 { typedef int bar; };

int main()
{
    std::cout << has_type_bar<foo1>::value << std::endl;
    std::cout << has_type_bar<foo2>::value << std::endl;
    return 0;
}