views:

245

answers:

4

I'm writing a template for which I'm trying to provide a specialization on a class which itself is a template class. When using it I'm actually instanciating it with derivitives of the templated class, so I have something like this:

template<typename T> struct Arg
{
    static inline const size_t Size(const T* arg) { return sizeof(T); }
    static inline const T*     Ptr (const T* arg) { return arg; }
};

template<typename T> struct Arg<Wrap<T> >
{
   static inline const size_t Size(const Wrap<T>* arg) { return sizeof(T); }
   static inline const T*     Ptr (const Wrap<T>* arg) { return arg.Raw(); }
};

class IntArg: public Wrap<int>
{
    //some code
}

class FloatArg: public Wrap<float>
{
    //some code
}
template<typename T>
void UseArg(T argument)
{
    SetValues(Arg<T>::Size(argument), Arg<T>::Ptr(&argument));
}

UseArg(5);
UseArg(IntArg());
UseArg(FloatArg());

In all cases the first version is called. So basically my question is: Where did I went wrong and how do I make him call the the version which returns arg when calling UseArg(5), but the other one when calling UseArg(intArg)? Other ways to do something like this (without changing the interface of UseArg) are of course welcome to.

As a note the example is a little simplyified meaning that in the actual code I'm wrapping some more complex things and the derived class has some actual operations.

A: 

typedef Wrap<int> IntArg; instead of deriving will solve your problem.

In fact the compiler doesn't search for specializations with base classes. Consider a simple example:

class A { };
class B: public A { };
template<class T>
void f(T const &)
{ std::cout << "T" << std::endl; }
template<>
void f<A>(A const&)
{ std::cout << "A" << std::endl; }

int main()
{ f(A()); f(B()); }

It prints "A T".

Nicht Verstehen
that might solve the problem but is NOT a template specialization.
Hassan Syed
Nor does it solve my problem since I really need to derive the classes
Grizzly
Hassan, what is partial specialization then? :)
Nicht Verstehen
http://www.cprogramming.com/tutorial/template_specialization.html . It's for the corner cases from C that had to be covered to represent the STL correctly -- e.g., itterators.
Hassan Syed
Ok, is `template<class T> class Z{}; template<class T> class Z<T*> {}` partial specialization?
Nicht Verstehen
A: 

As Nicht pointed out, the compiler doesn't search for specializations for any base classes.

Instead of specializing the Arg class, why don't you overload the UseArg function? Have a template version which takes a T, and have an overloaded version which takes a Wrap. The overloaded version can "unwrap" to the raw pointer as needed.

Terry Mahaffey
A: 

Instead of creating Arg which is a template you may make it regular class and then OVERLOAD Size and Ptr static members:

struct Arg
{
    template<class T>
    static const size_t Size(const T* arg) { return sizeof(T); }
    template<class T>
    static const size_t Size(const Wrap<T>* arg) { return sizeof(T); }
    template<class T>
    static const T*     Ptr (const T* arg) { return arg; }
    template<class T>
    static const T*     Ptr (const Wrap<T>* arg) { return arg->Raw(); }
};

EDIT: Well, it won't work, I've just checked...

Tomek
+1  A: 

I think there are three approaches:

1) Specialize Arg for derived types:

template <typename T> struct Arg ...
template <> struct Arg <IntArg> ...
template <> struct Arg <FloatArg> ...
// and so on ...

This sucks, because you can't know in advance what types you will have. Of course you you can specialize once you have these types, but this has to be done by someone who implements these types.

2) Do not provide default one and specialize for basic types

template <typename T> struct Arg;
template <> struct Arg <int> ...
template <> struct Arg <float> ...
// and so on...
template <typename T> struct Arg <Wrap<T> > ...

It's not ideal too (depends on how many "basic types" you expect to use)

3) Use IsDerivedFrom trick

template<typename T> struct Wrap { typedef T type; };
class IntArg: public Wrap<int> {};
class FloatArg: public Wrap<float> {};

template<typename D>
class IsDerivedFromWrap
{
    class No { };
    class Yes { No no[3]; }; 

    template <typename T>
    static Yes Test( Wrap<T>* ); // not defined
    static No Test( ... ); // not defined 

public:
    enum { Is = sizeof(Test(static_cast<D*>(0))) == sizeof(Yes) }; 
};


template<typename T, bool DerivedFromWrap> struct ArgDerived;

template<typename T> struct ArgDerived<T, false>
{
    static inline const T*     Ptr (const T* arg) { std::cout << "Arg::Ptr" << std::endl; return arg; }
};

template<typename T> struct ArgDerived<T, true>
{
    static inline const typename T::type* Ptr (const T* arg) { std::cout << "Arg<Wrap>::Ptr" << std::endl; return 0; }
};

template<typename T> struct Arg : public ArgDerived<T, IsDerivedFromWrap<T>::Is> {};

template<typename T>
void UseArg(T argument)
{
    Arg<T>::Ptr(&argument);
};

void test()
{
    UseArg(5);
    UseArg(IntArg());
    UseArg(FloatArg());
}

Calling test() outputs (as I understand that's your goal):

Arg::Size 
Arg<Wrap>::Size
Arg<Wrap>::Size

Extending it to work with more types like Wrap is possible, but messy too, but it does the trick - you don't need to do a bunch of specializations.

One thing worth mentioning is that in my code ArgDerived is specialized with 
IntArg instead of Wrap<int>, so calling sizeof(T) in ArgDerived<T, true> returns 
size of IntArg instead of Wrap<int>, but you can change it to sizeof(Wrap<T::type>) 
if that was your intention.

PS: I had to format text as code - otherwise stackowerflow hides anything between <> brackets:)
Paulius Liekis