Когда вы создаете экземпляр MyQueue<std::queue<int>>
, аргумент шаблона std::queue<int>
подставляется в шаблон класса. В объявлении функции-члена, которое приводит к использованию typename std::enable_if<false, void>::type
, который не существует. Это ошибка. Вы не можете объявить функцию, используя тип, который не существует.
Правильное использование enable_if
должно зависеть от параметра шаблона, который выводится . Во время вывода аргумента шаблона, если замена выведенного аргумента шаблона на параметр шаблона не удалась (то есть «ошибка замены»), то вы не получите немедленной ошибки, это просто приведет к сбою дедукции. Если вычет не удался, функция не подходит для разрешения перегрузки (но любые другие перегрузки все равно будут учитываться).
Но в вашем случае аргумент шаблона не выводится при вызове функции, он уже известен, потому что он исходит из окружающего шаблона класса. Это означает, что ошибка замещения является ошибкой, поскольку объявление функции некорректно до того, как вы попытаетесь выполнить разрешение перегрузки для его вызова.
Вы можете исправить свой пример, превратив функцию в шаблон функции, чтобы он имел параметр шаблона, который должен быть выведен:
template<typename T = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<T, void>::value,
void
>::type;
Здесь условие enable_if
зависит от T
вместо Queue
, поэтому, существует или нет элемент ::type
, неизвестно, пока вы не попытаетесь заменить аргумент шаблона для T
. Шаблон функции имеет аргумент шаблона по умолчанию, так что если вы просто вызываете notify_exit()
без списка аргументов шаблона, это эквивалентно notify_exit<Queue>()
, что означает, что условие enable_if
зависит от Queue
, как вы изначально хотели.
Эта функция может быть использована неправильно, так как вызывающие могут вызывать ее как notify_exit<SomeOtherType>()
, чтобы обмануть условие enable_if
в зависимости от неправильного типа. Если вызывающие делают это, они заслуживают ошибок компиляции.
Другим способом заставить код работать, было бы иметь частичную специализацию всего шаблона класса, просто удалить функцию, когда она не нужна:
template <typename Queue,
bool Notifiable
= has_member_function_notify_exit<Queue, void>::value>
class MyQueue
{
public:
void notify_exit();
Queue queue_a;
};
// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueue<Queue, false>
{
public:
Queue queue_a;
};
Вы можете избежать повторения всего определения класса дважды несколькими различными способами. Вы можете поднять весь общий код в базовый класс и добавить только член notify_exit()
в производный класс, который зависит от него. В качестве альтернативы вы можете переместить только условную часть в базовый класс, например:
template <typename Queue,
bool Notifiable
= has_member_function_notify_exit<Queue, void>::value>
class MyQueueBase
{
public:
void notify_exit();
};
// partial specialization for queues without a notify_exit member:
template <typename Queue>
class MyQueueBase<Queue, false>
{ };
template<typename Queue>
class MyQueue : public MyQueueBase<Queue>
{
public:
// rest of the class ...
Queue queue_a;
};
template<typename Queue, bool Notifiable>
void MyQueueBase<Queue, Notifiable>::notify_exit()
{
static_cast<MyQueue<Queue>*>(this)->queue_a.notify_exit();
}