8

What is the differences between these classes? Specifically, these member functions with enable_if.

/// Alias of std::enable_if...
template <bool B, typename T = void>
using Enable_if = typename std::enable_if<B, T>::type;

template<typename T, std::size_t N>
class A {
   ...
    template <std::size_t NN = N,
          typename = Enable_if<NN == 2>>
    Some_Return_Type
    method(param1, param2)
    {}

    template <std::size_t NN = N,
              typename = Enable_if<NN == 1>>
    Some_Return_Type
    method(param1)
    {}

};


template<typename T, std::size_t N>
class B {
   ...
    Enable_if<N == 2, Some_Return_Type>
    method(param1, param2)
    {}

    Enable_if<N == 1, Some_Return_Type>
    method(param1)
    {}
};

What is the correct way to use enable_if in the case I have:

  • At least 2 methods that differs only in their arguments and they have the same name but just one of them must be "active" (if N == 1 one of them, if N == 2 the other).
  • Only one method that will be active if N == 0 and not active in other cases.

I would like to get something like:

Obj<int, 2> obj2;
Obj<int, 0> obj0;
Obj<int, 1> obj1;

if I write in the IDE obj0. it should show only methods of:

  • N == 0;
  • for obj1., only N == 1
  • ...
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
Sam
  • 263
  • 2
  • 12

1 Answers1

7

Note that enable_if is aimed to trigger SFINAE: if a template parameter substitution fails in its immediate context, it is not a compilation error.

This is exaclty what happens in class A: when the user calls a.method(...), the compiler attempts to instantiate member function template method, subsituting NN parameter with a constant, which might fail.

However, in case of B::method the "bad" substitution occurs during class template B instantiation, when the compiler substitues N. The failure occurs far from the parameter's immediate context, which is in this case template<typename T, std::size_t N> class B.

That's why in the second case you will get a compilation error, rather than SFINAE.

So, to enable/disable member functions depending on the class template parameter, use the first approach, combining conditions as you wish. For instance:

template <typename T, std::size_t N>
class A {
    template <std::size_t NN = N, typename = std::enable_if_t<NN == 2 || NN == 0>>
    void method(int, int)
    {}

    template <std::size_t NN = N, typename = std::enable_if_t<NN == 1 || NN == 0>>
    void method(int)
    {}
};

UPDATE: How enable_if works. Roughly, one can implement it like this:

template<bool, class T = void>
struct enable_if {};

template<class T>
struct enable_if<true, T> {
    using type = T;
};

Note that if the first argument is false, enable_if doesn't have inner type, so enable_if<false, int>::type would be ill-formed. That's what triggers SFINAE.

Igor R.
  • 14,716
  • 2
  • 49
  • 83
  • The A class is the correct way to implement SFINAE, ok but I can't understand the second part. In A class if `NN == 2` (or `NN == 1`) `Enable_if` return void to the second typename, so the method is "defined" and I can call it. What does it return when substitution fails? How can it disable the method? And I can't understand why I get an error in B class. Thanks and sorry :( – Sam Aug 11 '19 at 18:05
  • @Sam see my update about `enable_if` mechanism. As for your 2nd question, I tried to answer it: you get error in `B`, because it is not SFINAE: Substitution Failure Is Not An Error only when it fails in [immediate context](https://stackoverflow.com/questions/15260685/what-exactly-is-the-immediate-context-mentioned-in-the-c11-standard-for-whic), wich is not the case with `B`. – Igor R. Aug 11 '19 at 18:26
  • The reason it fails for `B`, @Sam, is that it depends on _when_ the `enable_if` is evaluated. Basically, SFINAE in layman's terms if "if we can't build it, chuck it away and find one we _can_ build." In `A`, failure occurs when `method()` is called, because it depends on `method()`'s template parameters; this means that the compiler will examine every provided version of `method()`, in search of one that works. For `B`, it depends on `B`'s template parameters, and thus the compiler examines every provided version of `B` for one that works. But there's only _one_ `B`, so it can't find any. – Justin Time - Reinstate Monica Aug 11 '19 at 19:14
  • (This, in turn, is because of when the templates are instantiated. When you make an `A`, for example, the compiler will build it by filling in `A`'s template parameters wherever possible. In this case, that means filling in `method()`'s first template parameter's default value... but `method()` itself is still a template, and thus can't be compiled until it itself is instantiated or specialised (such as if you call `A::method()`, or define an explicit specialisation of `A::method<1>()`). Since the info isn't available until you use `method()`, SFINAE runs off `method`. – Justin Time - Reinstate Monica Aug 11 '19 at 19:23
  • When you make a `B`, though, the compiler can fill in the `N`s in `method()`'s return types, causing it to see overloads `Enable_if method(param1, param2)` and `Enable_if method(param1)`. The second is a substitution failure, meaning `B` is an invalid specialisation. This, in turn, causes an error due to the lack of a valid specialisation of `B` for the template parameters, before `B::method()` can be involved in SFINAE.) – Justin Time - Reinstate Monica Aug 11 '19 at 19:29
  • The disabled case for `std::enable_if` is an *empty* class, not an *incomplete* one. – Deduplicator Aug 12 '19 at 00:47
  • @Deduplicator why is it important? It would just affect the error message. – Igor R. Aug 12 '19 at 05:01
  • @IgorR. That's how it is specified, and code can depend on it. – Deduplicator Aug 12 '19 at 09:27
  • @Deduplicator do you mean that the standard explicitly specifies that? I'd appreciate the relevant link... – Igor R. Aug 12 '19 at 12:00
  • 2
    ["*If B is true, the member typedef type shall equal T; otherwise, there shall be no member type*"](http://eel.is/c++draft/meta.trans.other): Incomplete is a bit different from *no member*. – Deduplicator Aug 12 '19 at 13:14
  • @Jan Schultke instead of changing my answer you could add your one. – Igor R. Jun 29 '23 at 13:33
  • @IgorR. it's not worth it. Your answer already explains it well, there are just some details that can be improved. – Jan Schultke Jun 29 '23 at 14:42