When you instantiate MyQueue<std::queue<int>>
the template argument std::queue<int>
gets substituted into the class template. In the member function declaration that leads to a use of typename std::enable_if<false, void>::type
which does not exist. That's an error. You can't declare a function using a type that doesn't exist.
Correct uses of enable_if
must depend on a template parameter that is deduced. During template argument deduction, if substituting the deduced template argument for the template parameter fails (i.e. a "substitution failure") then you don't get an immediate error, it just causes deduction to fail. If deduction fails, the function isn't a candidate for overload resolution (but any other overloads will still be considered).
But in your case the template argument is not deduced when calling the function, it's already known because it comes from the surrounding class template. That means that substitution failure is an error, because the function's declaration is ill-formed before you even try to perform overload resolution to call it.
You can fix your example by turning the function into a function template, so it has a template parameter that must be deduced:
template<typename T = Queue>
auto notify_exit() -> typename std::enable_if<
has_member_function_notify_exit<T, void>::value,
void
>::type;
Here the enable_if
condition depends on T
instead of Queue
, so whether the ::type
member exists or not isn't known until you try to substitute a template argument for T
. The function template has a default template argument, so that if you just call notify_exit()
without any template argument list, it's equivalent to notify_exit<Queue>()
, which means the enable_if
condition depends on Queue
, as you originally wanted.
This function can be misused, as callers could invoke it as notify_exit<SomeOtherType>()
to trick the enable_if
condition into depending on the wrong type. If callers do that they deserve to get compilation errors.
Another way to make the code work would be to have a partial specialization of the entire class template, to simply remove the function when it's not wanted:
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;
};
You can avoid repeating the whole class definition twice in a few different ways. You could either hoist all the common code into a base class and only have the notify_exit()
member added in the derived class that depends on it. Alternatively you can move just the conditional part into a base class, for example:
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();
}
MyQueue
can also beMyQueueBase
, so thatclass MyQueue : public MyQueueBase<Queue>
could truly give the OP the conditional member they want, without repeating the class definition twice. I know I'm writing this under the answer of a standard library implementor, but I hope you will add it for the OP's benefit. Otherwise, fantastic answer. – Cree