Ответ 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
как вложенный тип.