1

I'm tyring to understand std::enable_if and the benefits of using it over a static_assert / regular template specialitzation.

After reading around I found:

This is useful to hide signatures on compile time when a particular condition is not met, since in this case, the member enable_if::type will not be defined and attempting to compile using it should fail. http://www.cplusplus.com/reference/type_traits/enable_if/

My question then is: Why the compiler blames me by saying that class C is already declared?, when only one of the declaraions should be avaiable at a time.

class Interface{};

class Foo : public Interface{};

template <class T, typename = typename std::enable_if<std::is_base_of<Interface,T>::value>::type>
class C{
   // Some custom implementation
}

template <class T, typename = typename std::enable_if<!std::is_base_of<Interface,T>::value>::type>
class C { 
  //Some fallback / default implementation
}

int main() {
    C<Foo> myVariable;
}

Same behaviour in Godbolt: https://godbolt.org/z/cbfhG9q54

Thanks in advance!

Aleix Rius
  • 74
  • 2
  • 9
  • Your `enable_if` is only setting the default value of the second argument of your class template `C`. Just like `foo(int x = 3);` and `foo(int x = 7);` can't be distinct overloads, just providing different default values to an argument doesn't mean it's declaring a different template or a template specialization. – Nathan Pierson Aug 16 '21 at 15:38
  • 1
    Class templates can't be *overloaded*. – songyuanyao Aug 16 '21 at 15:39
  • 2
    For example, if you decided to explicitly specify the value of the second argument of the template: `C ambiguousClass;`, which version of `C` should the compiler use? It doesn't even have to try to calculate `enable_if>` because you already told it you're going to use `void`, not the default value for that argument. – Nathan Pierson Aug 16 '21 at 15:41
  • Are you limited to older compilers? If you have a more current compiler, you can use `requires` and state what you want more directly and with more flexibility. – JDługosz Aug 16 '21 at 15:41
  • Then there is no real usage when it comes to classes, using a static_assert will have the same behavior. (Won't compile in both cases when the conditions is not met). – Aleix Rius Aug 16 '21 at 15:41
  • 1
    It can be useful for class template **specializations**. `template class C> {...}` – user253751 Aug 16 '21 at 15:46
  • 1
    It's still possible to get the effect you want, just not quite how you're using it. See e.g. [here](https://cpppatterns.com/patterns/class-template-sfinae.html), or [here](https://stackoverflow.com/questions/68058640/using-concepts-to-select-class-template-specialization) for a C++20 version. – Nathan Pierson Aug 16 '21 at 15:56
  • 1
    This mistake is described as "a common mistake" in [the cppreference notes for `std::enable_if`](https://en.cppreference.com/w/cpp/types/enable_if#Notes). – Drew Dormann Aug 16 '21 at 16:04
  • Thanks for all the answers, now I understand a little bit better! – Aleix Rius Aug 16 '21 at 16:04

1 Answers1

3

You cannot overload class templates like you can function templates, buy you can partially specialize them (which you cannot do with function templates):

#include <ios>
#include <iostream>
#include <type_traits>

class Interface
{};
class Foo : public Interface
{};

template <class T, typename = void>
struct C
{
    // Some default impl.
    static constexpr bool is_default_impl{true};
};

template <class T>
struct C<T, std::enable_if_t<std::is_base_of_v<Interface, T>>>
{
    // Some custom implementation.
    static constexpr bool is_default_impl{false};
};

int main()
{
    std::cout << std::boolalpha 
        << C<int>::is_default_impl << " "  // true
        << C<Foo>::is_default_impl;        // false
}

Note that this examples requires C++17 for the variable template std::is_base_of_v which is a short-hand constant the value member of the std::is_base_of trait, and C++14 for the alias template std::enable_if_t, which aliases the type member alias declaration of the std::enable_if trait.

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 1
    I reached the same solution after all the comments the people did. Thanks for writting it up, I'm sure it will help someone in the future! – Aleix Rius Aug 16 '21 at 16:03
  • Beware some obscure bugs in MSVC where the partial specializations may be skipped if the primary template compiles for a given template parameter. Better to leave the primary template a pure forward declaration, and make sure the specializations don't overlap in their `enabled_if` clauses... – Ext3h Aug 16 '21 at 16:35