29

Let's say I have a two classes: Serializable and Printable.

So a simple template function which accepts all derived classes of Printable could look like:

template <class T, class B = Printable, class = typename std::enable_if<std::is_base_of<B,     T>::value>::type>
void print(T value) {
    cout << value << endl;
}

However, if I want it to accept also all derived classes of Serializable while I still have control over the function body, this would obviously not work:

template <class T, class B = Printable, class = typename std::enable_if<std::is_base_of<B,     T>::value>::type>
void print(T value) {
    cout << value << endl;
}

template <class T, class B = Serializable, class = typename std::enable_if<std::is_base_of<B,     T>::value>::type>
void print(T value) {
    cout << value << endl;
}

// Error: Redefinition of ...

So I figured the remaining solutions for this problem are template specializations.

But I just can't figure out, how I can specialize a template in combination with std::is_base_of and std::enable_if.

I hope someone is willing to help me!

Tim
  • 5,521
  • 8
  • 36
  • 69

3 Answers3

24

Try a logical operator:

std::enable_if<std::is_base_of<Serializable, T>::value ||
               std::is_base_of<Printable, T>::value>::type

You can easily write a variadic template like:

is_base_of_any<T, Printable, Serialiable, Googlable, Foobarable>::value

For example:

template <typename T, typename ...> struct is_base_of_any : std::true_type {};

template <typename T, typename Head, typename ...Rest>
struct is_base_of_any<T, Head, Rest...>
: std::integral_constant<bool, std::is_base_of<T, Head>::value ||
                               is_base_of_any<T, Rest...>::value>
{ };

If you want different implementations:

template <bool...> struct tag_type {};

template <typename T>
void foo(T, tag_type<true, false>) { }   // for Printable

template <typename T>
void foo(T, tag_type<false, true>) { }   // for Serializable

template <typename T>
void foo(T x)
{
    foo(x, tag_type<std::is_base_of<Printable, T>::value,
                    std::is_base_of<Serializable, T>::value>());
}

The last overload (the "user-facing" one) should probably be endowed with the above enable_if to not create overly many overload candidates.

You can probably also make a variadic template <typename ...Bases> with a tag like:

tag_type<std::is_base_of<Bases, T>::value...>
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
23

A little less machinery than Kerrek's answer, but I'm afraid no more readable:

template <class T, typename std::enable_if<std::is_base_of<Printable, T>::value>::type* = nullptr>
void print(const T& value) {
    std::cout << "printable(" << &value << ")\n";
}

template <class T, typename std::enable_if<std::is_base_of<Serializable, T>::value>::type* = nullptr>
void print(const T& value) {
    std::cout << "serializable(" << &value << ")\n";
}

See it live at ideone.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • This actually is the best solution for my problem, since the code is machine generated. Wish I could accepts two answers! Thanks! – Tim Jun 19 '13 at 21:24
  • @KerrekSB It does. But I think your solution suits other people with the same question better. – Tim Jun 19 '13 at 21:49
  • @Casey Is there some way to explicit choose a overload compililation-time? Like `print(derived)` – Tim Jun 19 '13 at 21:54
  • @Tim `print(t)` should select the overload for `Base`. Yes, it's completely hideous. – Casey Jun 19 '13 at 21:59
  • @Casey Hey Casey, is this also possible if the base is an template class while it doesn't matter which parameters it takes? e.g. `Iteratable` – Tim Jun 23 '13 at 13:11
  • This no longer works with GCC 6.3 on ideone - it gives `error: redefinition of ‘template void print(const T&)’` – Eric Mar 29 '18 at 22:11
  • I didn't say it was - I'm just point out that your ideone no longer compiles – Eric Mar 30 '18 at 22:03
  • @Eric Aha, thank you! I didn't realize the link was out of sync with the answer. Should be fixed now. – Casey Apr 01 '18 at 01:29
  • @Nik-Lz The second/long template parameters apply constraints to the first `T` parameters. They're only valid when `T` has the required property, and otherwise cause substitution failure removing the corresponding overload from overload resolution. – Casey Aug 29 '18 at 14:40
  • 1
    @Nik-Lz Only the first template parameter `T` is a type parameter. The second template parameter in both cases is a non-type parameter of type pointer-to-void when the `enable_if` condition is satisfied - and pointer-to-substitution-failure otherwise - whose value defaults to `nullptr`. – Casey Aug 29 '18 at 15:38
  • 1
    @Nik-Lz The sole purpose of the unnamed second parameter is to cause substitution failure when `T` doesn't meet the requirements for the overload, effectively removing that overload from the overload set in that circumstance. The intent here is that we have two otherwise identical overloads of `print one of which will be selected by overload resolution when the type of the function parameter derives from `Printable`, and another which will be selected the the type of the function parameter derives from `Serializable`. – Casey Aug 30 '18 at 18:31
2

Consider this:

void print(const Printable& value) {
    cout << value << endl;
}

void print(const Serializable& value) {
    cout << value << endl;
}

Naturally you will have the appropriate operator<< calling a virtual function in the right hand side operand, which would do the actual printing.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243