tags:

views:

115

answers:

5

I wondering is there any way to set restrictions on template class?
Specify that every type substituted in template must have specific ancestor (realize some interface).

template < class B > //and every B must be a child of abstract C
class A {
public:
    B * obj;
    int f() {
        return B::x + this->obj->f();
    }
};

Like => in haskell

func :: (Ord a, Show b) => a -> b -> c
+2  A: 

You can use BOOST_STATIC_ASSERT or a similar library to assert your restrictions on the template parameter.

For example:#include

#include <boost/static_assert.hpp>

template <class UnsignedInt>
class myclass
{
private:
   BOOST_STATIC_ASSERT((std::numeric_limits<UnsignedInt>::digits >= 16)
                        && std::numeric_limits<UnsignedInt>::is_specialized
                        && std::numeric_limits<UnsignedInt>::is_integer
                        && !std::numeric_limits<UnsignedInt>::is_signed);
public:
   /* details here */
};

EDIT: For your example, you can write

template < class B >
class A {
    BOOST_STATIC_ASSERT(boost::is_base_of<C, B>);

public:
    B * obj;
    int f() {
        return B::x + this->obj->f();
    }
};
SLaks
+2  A: 

You could use a trick like this (if you don't want to use Boost):

class Base
   {
   public:
      static const int TEMPLATE_REQUIRES_BASE_CLASS = 0;
   };

class Correct : public Base
   {
   };

class Incorrect
   {
   };

template <typename T>
class TMPL
   {
   static const int TEMPLATE_REQUIRES_BASE_CLASS = T::TEMPLATE_REQUIRES_BASE_CLASS;
   T *m_t;
   };

void main()
{
TMPL<Correct> one;      // OK
TMPL<Incorrect> two;    // Will not compile
}

The first line will compile. The second will not compile and will give the following error:

test.cpp
test.cpp(18) : error C2039: 'TEMPLATE_REQUIRES_BASE_CLASS' : is not a member of 'Incorrect'
        test.cpp(12) : see declaration of 'Incorrect'
        test.cpp(25) : see reference to class template instantiation 'TMPL<T>' being compiled
        with
        [
            T=Incorrect
        ]
test.cpp(18) : error C2065: 'TEMPLATE_REQUIRES_BASE_CLASS' : undeclared identifier
test.cpp(18) : error C2057: expected constant expression
Patrick
This code doesn't fail under g++ 4.4, due to some unknown reason. In general, I'd say that in this case, an enum is usually used: enum { TEMPLATE_REQUIRES_BASE_CLASS = 0 };This has the additional benefit of failing as expected under g++.
Semen Semenych
@Semen, GCC is non-conforming. The Standard requires the declaration of the data member to be instantiated when the class is implicitly instantiated, and an in-class initializer is part of the declaration instead of the definition of it. However the Standard is slightly unclear, saying "the initialization (and any associated side-effects) of a static data member does not occur unless the static data member is itself used in a way that requires the definition of the static data member to exist." - this rule however seems to apply only to an out-of-class initializer (makes no sense otherwise)
Johannes Schaub - litb
@Johannes: thanks for the explanation, I thought I was going crazy when it built fine... but I guess enum is still simpler to use. (I'm just following Vandervourde :)
Semen Semenych
+1  A: 

A future version of C++ will support this natively using concepts (which didn't make it into C++0x).

One way to approach the problem is to use specialisation on a dummy template parameter:

class C {};
template <class B, class dummy=void>
class A;

template <class B>
class A<B, typename enable_if<is_base_and_derived<C, B> >::type>
{
    // class definition here
};

struct D : C {};

A<D> d;     // fine
A<int> n;   // compile error - undefined class A<B>

I've put stand-alone definitions of enable_if and is_base_and_derived here.

James Hopkin
Thanks, awesome piece of code
Andrew
+1  A: 

Templates are sort of duck typing in C++.

If your class supports everything that the template uses, then it can be used as template argument, otherwise it cannot.

If in your template you have something like

C *instance;

void foo(T *t)
{
  instance = t;
}

then you're enforcing that T is derived from C (or at least assignement-compatible for pointers)

Arkadiy
A: 

The following works in VC10 using static_assert. I've just seen this used and haven't really dug around much into what static_assert actually does - perhaps someone else can answer that.

#include <type_traits>

class Base
{

};

class Derived : public Base
{

};

class SomeRandomClass
{

};

template<typename T>
class A
{
    static_assert(std::tr1::is_base_of<Base, T>::value, "T not derived from Base");
};



int _tmain(int argc, _TCHAR* argv[])
{
    argc; argv;  

    //
    //  This will compile
    A<Derived> a;

    //
    //  This will throw a compilation error
    A<SomeRandomClass> b; 

    return 0;
}

The compiler output being:

1>d:\temp\aaa\aaa\aaa.cpp(25): error C2338: T not derived from Base
1>          d:\temp\aaa\aaa\aaa.cpp(41) : see reference to class template instantiation 'A<T>' being compiled
1>          with
1>          [
1>              T=SomeRandomClass
1>          ]
obelix