This code works fine:
#include <type_traits>
using namespace std;
enum class Type : char { Void };
struct FieldA { static constexpr Type type = Type::Void; };
template<typename Field> struct Signal {};
template<typename Field> struct SignalA : public Signal<Field> {};
struct SignalB : public Signal<void> {};
struct DerivedB : public SignalB {};
template<typename Signal, typename = void> struct Apply;
template<typename Field>
struct Apply<SignalA<Field>, typename std::enable_if<Field::type == Type::Void>::type> {};
template<typename Signal_t>
struct Apply<Signal_t, typename enable_if<is_base_of<SignalB, Signal_t>::value>::type>
{};
int main ()
{ Apply<SignalA<FieldA> > a; }
But what I want is improve readability erasing the long enable_if
s, so, let's attack, for example, the second enable_if
with an auxiliary class:
template<typename Signal>
struct IsBaseOfB
{ using type = typename enable_if<is_base_of<SignalB, Signal>::type; };
And change the second Apply
partial specialization with it:
template<typename Signal_t>
struct Apply<Signal_t, typename IsBaseOfB<Signal_t>::type>
{};
Even when the only possible specialization is the first one, gcc
throws me the following error:
main.cpp: In instantiation of 'struct IsBaseOfB<SignalA<FieldA> >':
main.cpp:21:78: error: no type named 'type' in 'struct std::enable_if<false, void>'
{ using type = typename enable_if<is_base_of<SignalB, Signal_t>::value>::type; };
Which is obvious since the enable_if
condition doesn't fit for SignalA<FieldA>
.
Which I don't understand is why that specialization failure is not ignored to get the first specialization (which I know it works).
The error is not in "the immediate context" of the template argument deduction, and so SFINAE does not apply (i.e. it's an error, not a substitution failure).
The C++ standard doesn't really define "immediate context" but I have attempted to give a hand-wavy explanation in the accepted answer to What is exactly the “immediate context” mentioned in the C++11 Standard for which SFINAE applies?
Briefly, the problem is that the compiler sees Apply<SignalA<FieldA>>
and happily substitutes the template arguments into struct Apply<Signal_t, typename IsBaseOfB<Signal_t>::type>
which happens without an error because IsBaseOf
does have a nested type
member, but then that triggers the instantiation of IsBaseOf::type
and that's ill-formed. The error is not in the immediate context of the deduction (it's elsewhere in the body of IsBaseOf
which is instantiated as a side effect).
Another way to look at the problem is that IsBaseOf::type
is declared unconditionally, not only when the is_base_of
trait is true, so it is always declared as a type ... but sometimes its definition is ill-formed. The deduction finds the declaration and continues past the point where SFINAE applies, then a fatal error is found when the definition of IsBaseOf::type
is required.
You can either solve it using an alias template (as shown in Filip's answer) or using inheritance, so that IsBaseOf::type
only exists conditionally:
template<typename Signal>
struct IsBaseOfB : enable_if<is_base_of<SignalB, Signal>::value>
{ };
This way the nested type
member is only present when the enable_if
base class declares it.