Question

Can I specify exactly what kind of arguments a template can receive? For example, I'd like to create a template that can only be instantiated with classes that are or extend class A. In Java, generics support this with:

class B<T extends A> { }

Can something similar be achieved with templates in C++?

template <typename T (?)> class B { }
Was it helpful?

Solution

There are two ways to do this.

First, through a hidden dummy template parameter that uses std::enable_if with a std::is_base_of<A, T>::value as condition. If the latter expression evaulates to false, then the nested type does not exist in std::enable_if. If you were using this on overloaded functions, SFINAE then means "substitution failure is not an error", and the overload in question would be removed from the set of viable functions. HOwever in this situation, there is no other class template to match your call, and then you do get a compile-time error.

SFINAE is a very subtle mechanism and easy to get wrong. E.g. if you have multiple class specializations with different SFINAE conditions, you have to make sure that they are all non-overlapping, or else you get an ambiguity.

Second, you can do a simple static_assert with a std::is_base_of<A,T>::value inside the body of the class. The advantage of this method is that you also specify a more readable error message compared to the SFINAE method. A disadvantage is that you always get an error, and you cannot silently suppress this particular template and select another one. But overall I think this method is recommended in your case.

#include<type_traits>

class A {};
class C: public A {};
class D {};

// first alternative: SFINAE on hidden template parameter
template
<
    typename T, 
    typename /* dummy */ = typename std::enable_if< 
        std::is_base_of<A, T>::value
    >::type
>
class B
{
};

// second alternative: static_assert inside class
template
<
    typename T
>
class E
{
    static_assert(std::is_base_of<A, T>::value, "A should be a base of T");
};

int main()
{
    B<A> b1;
    B<C> c1;
    //B<D> d1; // uncomment this line to get a compile-time error

    E<A> b2;
    E<C> c2;
    //E<D> d2; // uncomment this line to get a compile-time error

    return 0;
}

As was pointed out in the comments, you can use either a decent C++11 compiler (VC++ 2010 or later, gcc 4.5 or later) or the Boost or TR1 libraries to get the <type_traits> functionality. Note however that the std::is_base_of<A, A>::value evaluates to true, but the old boost::is_base_of<A, A>::value used to evalute to false.

OTHER TIPS

You can do this with static_assert and is_base_of:

#include <type_traits>
template<typename T> class D {
    static_assert(std::is_base_of<A, T>::value, "must be derived from A");
};

Or you can use enable_if:

#include <type_traits>
template<typename T, typename = void> class D;
template<typename T> class D<T, typename std::enable_if<std::is_base_of<A, T>::value>::type> {
};

For C++03 you can use boost; is_base_of from Boost.TypeTraits, static_assert from Boost.StaticAssert, enable_if from Boost.EnableIf.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top