3

I need to implement two different methods for const and non-const types. I have manage to write working code but I do not understand why some of its flavors are OK and some of them are not.

Here is simplified example and I would like to know why #1 works but #2 is not, and same regarding #3 vs #4:

#include <iostream>
#include <vector>

template <typename T>
class X {
public:
    // #1 - works
    template<typename B = T, typename std::enable_if<std::is_const<B>::value, int>::type = 0>
    void foo() {std::cout << "CONST" << std::endl;}
    template<typename B = T, typename std::enable_if<std::is_const<B>::value == false, int>::type = 0>
    void foo() {std::cout << "NON-CONST" << std::endl;}

    // #2 - does not work "no type named 'type' in 'std::__1::enable_if<false, int>'; 'enable_if' cannot be used to disable this declaration"
//    template<typename std::enable_if<std::is_const<T>::value, int>::type = 0>
//    void foo() {std::cout << "CONST" << std::endl;}
//    template<typename std::enable_if<std::is_const<T>::value == false, int>::type = 0>
//    void foo() {std::cout << "NON-CONST" << std::endl;}

    // #3 - works
//    template<typename B = T, typename = typename std::enable_if<std::is_const<B>::value>::type>
//    void foo() {std::cout << "CONST" << std::endl;}
//    template<typename B = T, typename std::enable_if<std::is_const<B>::value == false>::type * = nullptr>
//    void foo() {std::cout << "NON-CONST" << std::endl;}

    // # 4 - does not work - "class member cannot be redeclared"
//    template<typename B = T, typename = typename std::enable_if<std::is_const<B>::value>::type>
//    void foo() {std::cout << "CONST" << std::endl;}
//    template<typename B = T, typename = typename std::enable_if<std::is_const<B>::value == false>::type>
//    void foo() {std::cout << "NON-CONST" << std::endl;}
};

int main() {
    X<int> v;
    X<const int> vConst;

    v.foo();
    vConst.foo();

    return 0;
}

Even if there is a better approach to solve my problem I would really like to understand why enable_if works like it (not) works in presented examples.

Krzysztof
  • 769
  • 8
  • 27
  • Just a matter of style, but prefer having `!condition` instead of `condition == false`... – Aconcagua Aug 22 '18 at 08:22
  • @Aconcagua: sure, usually I use `!condition` but unfortunately templates code can be so complicated that `condition == false` is much more visible/readable for me. – Krzysztof Aug 22 '18 at 08:27
  • 2
    Possible duplicate of [Why doesn't SFINAE (enable\_if) work for member functions of a class template?](https://stackoverflow.com/questions/30953248/why-doesnt-sfinae-enable-if-work-for-member-functions-of-a-class-template) – Daniel Langr Aug 22 '18 at 08:29
  • Or, likely a better duplicate match: https://stackoverflow.com/q/6972368/580083 – Daniel Langr Aug 22 '18 at 08:38
  • Side note: You might cover the issue more easily with constexpr if inside a non-template function (just for the case you are not aware of...). – Aconcagua Aug 22 '18 at 08:53

1 Answers1

7

#2 doesn't works as your have hard failure as T is fixed by the class.

so you really have

template<typename std::enable_if<true, int>::type = 0>  // -> template<int = 0>  void foo();
void foo();

template<typename std::enable_if<false, int>::type = 0> // Hard error failure
void foo();

For #4, default template value/type are not part of the signature, so once those removed, you have

template <typename B, typename> void foo() {std::cout << "CONST" << std::endl;}
template <typename B, typename> void foo() {std::cout << "NON-CONST" << std::endl;}

Same method with several definitions: you break ODR.

For #3, it would be:

template<typename B, typename>
void foo();

template<typename B, typename std::enable_if<std::is_const<B>::value == false>::type *>
void foo();

which are different.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Perhaps interesting at #3 to illustrate the difference more clearly: *if* second `foo` *is* enabled, it would get to `` – Aconcagua Aug 22 '18 at 08:47
  • 1
    @Aconcagua: but signature **differs** before that substitution, for example [Demo](http://coliru.stacked-crooked.com/a/0776478b57a35083) produces ambiguous call (instead of ODR violation, as your sentence would suggest). – Jarod42 Aug 22 '18 at 08:54
  • Don't see why my comment would suggest ODR violation, my intention *was* to make the differing signature more obvious... – Aconcagua Aug 22 '18 at 09:13
  • I meant that `typename , int>> voif f()` and `template void f()` are 2 different signatures, even if after substitution, they result in same. – Jarod42 Aug 22 '18 at 09:21
  • Ah, I see... got misguided by your emphasis ("but signature differs even *before* that substitution" would have been more obvious), but interesting aspect... – Aconcagua Aug 22 '18 at 09:54