11

I need to create a template function like this:

template<typename T>
void foo(T a)
{
   if (T is a subclass of class Bar)
      do this
   else
      do something else
}

I can also imagine doing it using template specialization ... but I have never seen a template specialization for all subclasses of a superclass. I don't want to repeat specialization code for each subclass

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
Abhishek Anand
  • 3,789
  • 2
  • 21
  • 29

6 Answers6

15

You can do what you want but not how you are trying to do it! You can use std::enable_if together with std::is_base_of:

#include <iostream>
#include <utility>
#include <type_traits>

struct Bar { virtual ~Bar() {} };
struct Foo: Bar {};
struct Faz {};

template <typename T>
typename std::enable_if<std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
    std::cout << type << " is derived from Bar\n";
}
template <typename T>
typename std::enable_if<!std::is_base_of<Bar, T>::value>::type
foo(char const* type, T) {
    std::cout << type << " is NOT derived from Bar\n";
}

int main()
{
    foo("Foo", Foo());
    foo("Faz", Faz());
}

Since this stuff gets more wide-spread, people have discussed having some sort of static if but so far it hasn't come into existance.

Both std::enable_if and std::is_base_of (declared in <type_traits>) are new in C++2011. If you need to compile with a C++2003 compiler you can either use their implementation from Boost (you need to change the namespace to boost and include "boost/utility.hpp" and "boost/enable_if.hpp" instead of the respective standard headers). Alternatively, if you can't use Boost, both of these class template can be implemented quite easily.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    @Cat Plus Plus: er, yes... Thanks for fixing it! ;-) – Dietmar Kühl Jan 08 '12 at 03:38
  • 1
    I believe that `::std::enable_if` is only part of C++ in C++11. It can be found in Boost though. – Omnifarious Jan 08 '12 at 03:50
  • 1
    @Omnifarious yes, both `std::enable_if` and `std::is_base_of` are "only" part of the current C++ standard (C++2011), not its predecessor (C++2003). If you have to use a compiler which isn't conforming the current standard, you can still implement a version of both `enable_if` and `is_base_of`: they are both fairly trivial to implement. However, [Boost](http://www.boost.org/) indeed provides an implementation. – Dietmar Kühl Jan 08 '12 at 03:59
  • I'm having issues with my compiler on compiling this . Could you please provide the boost version same? Thank you so much! – Abhishek Anand Jan 08 '12 at 04:14
  • 1
    @AbhishekAnand: you mean the Boost version of the above code? Well, I could but it is exactly identical except that you need to replace `std` by `boost`. Obviously, you also need to include the relevant headers. In C++2011 these are `` and ``. In Boost they are `"boost/utility.hpp"` and `"boost/type_traits.hpp"`. If this doesn't help, please let me know the compiler error - maybe there is a typo somewhere... – Dietmar Kühl Jan 08 '12 at 04:20
  • @DietmarKühl: with minor changes, it compiled: `boost::enable_if,T>::type` . Thanks – Abhishek Anand Jan 08 '12 at 04:29
  • @DietmarKühl: Yes, it is the current version of the standard. I'm not going to begin assuming that people have a compiler that supports it until around 2013 or 2014 or so though. Stuff like this moves kinda slow. Before the Internet it was even slower and I'd be waiting for about 7 years before making the assumption. – Omnifarious Jan 08 '12 at 05:13
  • @Omnifarious: I'd think the time it takes until a standard is implemented is proportional to the inverse of the number of people asking vendors for it and proportional to the time the fastest competition needs. gcc and clang are getting pretty close to implementing C++2011. – Dietmar Kühl Jan 08 '12 at 05:18
5

I would use std::is_base_of along with local class as :

#include <type_traits>  //you must include this: C++11 solution!

template<typename T>
void foo(T a)
{
   struct local
   {
        static void do_work(T & a, std::true_type const &)
        {
            //T is derived from Bar
        }
        static void do_work(T & a, std::false_type const &)
        {
            //T is not derived from Bar
        }
   };

   local::do_work(a, std::is_base_of<Bar,T>());
}

Please note that std::is_base_of derives from std::integral_constant, so an object of former type can implicitly be converted into an object of latter type, which means std::is_base_of<Bar,T>() will convert into std::true_type or std::false_type depending upon the value of T. Also note that std::true_type and std::false_type are nothing but just typedefs, defined as:

typedef integral_constant<bool, true>  true_type;
typedef integral_constant<bool, false> false_type;
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Wouldn't `local::do_work(a, std::is_base_of());` work? – Cat Plus Plus Jan 08 '12 at 03:53
  • @CatPlusPlus: What would the type of second parameter of `do_work`? – Nawaz Jan 08 '12 at 03:57
  • @CatPlusPlus: Oops, I didn't know `std::is_base_of` derives from `std::integral_constant`. Yes it will work too. – Nawaz Jan 08 '12 at 03:59
  • 1
    Unfortunately this doesn't work on all compilers. GCC and Clang don't instantiate local classes or members of them if they are unused. But Comeau and perhaps other compilers (based on EDG, at least intel's ICC) do (and indeed I'm not sure what compiler is correct, but I tend to think EDG is correct in this case). That would mean your workaround doesn't work. – Johannes Schaub - litb Feb 22 '12 at 18:08
4

I know this question has been answered but nobody mentioned that std::enable_if can be used as a second template parameter like this:

#include <type_traits>

class A {};
class B: public A {};

template<class T, typename std::enable_if<std::is_base_of<A, T>::value, int>::type = 0>
int foo(T t)
{
    return 1;
}
Elviss Strazdins
  • 1,404
  • 14
  • 30
  • I also found that using enable_if_t and is_base_of_v is more convenient. So, we don't have to put ::type and ::value postfixes. – sang Mar 03 '21 at 02:45
  • @sang enable_if_t and is_base_of_v is available only in C++14 and is_base_of_v was introduced in C++17. The question was asked 9 years when C++11 was the most recent version of the language, so I gave an answer that works with it. – Elviss Strazdins Mar 17 '21 at 00:19
  • Ah, I see. Thanks for the clarification. – sang Mar 18 '21 at 02:01
3

I like this clear style:

void foo_detail(T a, const std::true_type&)
{
    //do sub-class thing
}

void foo_detail(T a, const std::false_type&)
{
    //do else
}

void foo(T a)
{
    foo_detail(a, std::is_base_of<Bar, T>::value);
}
nttstar
  • 341
  • 2
  • 10
1

The problem is that indeed you cannot do something like this in C++17:

template<T>
struct convert_t {
    static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T> {
  // T should be subject to the constraint that it's a subclass of X
}

There are, however, two options to have the compiler select the correct method based on the class hierarchy involving tag dispatching and SFINAE.

Let's start with tag dispatching. The key here is that tag chosen is a pointer type. If B inherits from A, an overload with A* is selected for a value of type B*:

#include <iostream>
#include <type_traits>

struct type_to_convert {
    type_to_convert(int i) : i(i) {};
    type_to_convert(const type_to_convert&) = delete;
    type_to_convert(type_to_convert&&) = delete;
    int i;
};

struct X {
    X(int i) : i(i) {};
    X(const X &) = delete;
    X(X &&) = delete;
public:
    int i;
};
struct Y : X {
    Y(int i) : X{i + 1} {}
};
struct A {};

template<typename>
static auto convert(const type_to_convert &t, int *) {
    return t.i;
}
template<typename U>
static auto convert(const type_to_convert &t, X *) {
    return U{t.i}; // will instantiate either X or a subtype
}

template<typename>
static auto convert(const type_to_convert &t, A *) {
    return 42;
}

template<typename T /* requested type, though not necessarily gotten */>
static auto convert(const type_to_convert &t) {
    return convert<T>(t, static_cast<T*>(nullptr));
}

int main() {
    std::cout << convert<int>(type_to_convert{5}) << std::endl;
    std::cout << convert<X>(type_to_convert{6}).i << std::endl;
    std::cout << convert<Y>(type_to_convert{6}).i << std::endl;
    std::cout << convert<A>(type_to_convert{-1}) << std::endl;
    return 0;
}

Another option is to use SFINAE with enable_if. The key here is that while the snippet in the beginning of the question is invalid, this specialization isn't:

template<T, typename = void>
struct convert_t {
    static auto convert(T t) { /* err: no specialization */ }
}
template<T>
struct convert_t<T, void> {
}

So our specializations can keep a fully generic first parameter as long we make sure only one of them is valid at any given point. For this, we need to fashion mutually exclusive conditions. Example:

template<typename T /* requested type, though not necessarily gotten */,
         typename = void>
struct convert_t {
    static auto convert(const type_to_convert &t) {
        static_assert(!sizeof(T), "no conversion");
    }
};

template<>
struct convert_t<int> {
    static auto convert(const type_to_convert &t) {
        return t.i;
    }
};

template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<X, T>>> {
    static auto convert(const type_to_convert &t) {
        return T{t.i}; // will instantiate either X or a subtype
    }
};

template<typename T>
struct convert_t<T, std::enable_if_t<std::is_base_of_v<A, T>>> {
    static auto convert(const type_to_convert &t) {
        return 42; // will instantiate either X or a subtype
    }
};

template<typename T>
auto convert(const type_to_convert& t) {
    return convert_t<T>::convert(t);
}

Note: the specific example in the text of the question can be solved with constexpr, though:

template<typename T>
void foo(T a) {
   if constexpr(std::is_base_of_v<Bar, T>) 
      // do this
   else
      // do something else
}
Artefacto
  • 96,375
  • 17
  • 202
  • 225
0

If you are allowed to use C++20 concepts, all this becomes almost trivial:

template<typename T> concept IsChildOfX = std::is_base_of<X, T>::value;
// then...
template<IsChildOfX X>
void somefunc( X& x ) {...}
Steve
  • 21
  • 4