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 cannot know in advance what types you will have. Of course, you can specialize as soon as you have these types, but you need to do this with the help of those who implement these types.
2) Do not provide default defaults and specialize in 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> > ...
This is not ideal either (depending on how many “core types” you expect to use)
3) Use the 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 it, your goal):
Arg::Size Arg<Wrap>::Size Arg<Wrap>::Size
Extending it to work with a large number of types, such as Wrap, is possible, but randomly, but it is a trick - you do not need to do a lot of specializations.
It should be mentioned that in my code ArgDerived specializes in IntArg instead of Wrap<int> , so calling sizeof(T) on ArgDerived<T, true> returns the size of IntArg instead of Wrap<int> , but you can change it to sizeof(Wrap<T::type>) if that was your intention.