1

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_ifs, 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).

ABu
  • 10,423
  • 6
  • 52
  • 103

2 Answers2

5

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.

Community
  • 1
  • 1
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • The problem with the `using` keyword is that it can't be used to instantiate dependent types (till C++17 though), so the inheritage approach is the best one now I think – ABu Mar 11 '15 at 12:20
  • Not sure what you mean, the alias template in the other answer should work in C++11 – Jonathan Wakely Mar 11 '15 at 12:22
4

Introduction

SFINAE only applies if the substitution error is in the immediate context of template instantiation, if the substitution error occurs in the body of said template the program is ill-formed.


Solution

Either you make the substitution error occur in the immediate context of the instantiation of template<typename> struct IsBaseOfB, or you simply define a template alias-declaration that will be equivalent to your rather lengthy trait.


template alias-declaration (recommended)

template<typename Signal>
using IsBaseOfB = enable_if<is_base_of<SignalB, Signal>::value>;


using inheritance

template<typename Signal>
struct IsBaseOfB
  : enable_if<is_base_of<SignalB, Signal>::value>
{ };
Community
  • 1
  • 1
Filip Roséen - refp
  • 62,493
  • 20
  • 150
  • 196