Using partial specialization of templates I would like to create a function/method:
A) processing only one specific primitive type (int, double, float,...) of the formal parameter and for other types throwing exception
template <class T>
T min ( Point <T> p )
{
/*if (T == int) continue;
else throw exception*/
}
B) processing more non-primitive types (user defined types) of the formal parameter and for other types throwing exception...
Some code examples would be helpful (without c++ boost library). Thanks for your help.
This is the solution of your problem (part A).
template<bool b> struct compile_time_assert;
template<>
struct compile_time_assert<true>
{};
template<class T>
struct is_int
{
static const bool value = false;
};
template<>
struct is_int<int>
{
static const bool value = true;
};
template <class T>
T min ( Point <T> p )
{
/*
since you can check error at compile time, there is no reason to
raise exception (which is at runtime) if T is not int!
the following assert will not compile if T is not int.
not only that, you can even see the error message "_error_T_is_not_int"
if T is not int;
*/
compile_time_assert<is_int<T>::value> _error_T_is_not_int;
//your code
}
See these sample code.
- sample
code1
when T is int. No error. Please ignore the
warning though!
- sample
code2
when T is double. Now, see the error
message also. Please ignore the
warning though!
Similarly, you can write templates for other types, (double, char, whatever). Or, even better, you can simply merge all those into just one struct
, and instead can define many booleans (for each type) in a single template, like static const bool is_T_int = true;
. Etc. Do experiments, you'll learn!
But then, I wonder if you want your function to handle just one type, then why define template to start with?
For part(B), you get the idea from the above. Right? Do experiments!
you can use boost::mpl here for part B but this is use boost:(
#include <boost/mpl/at.hpp>
#include <boost/mpl/map.hpp>
#include <boost/mpl/bool.hpp>
using namespace boost;
typedef mpl::map<
mpl::pair<int, mpl::true_>,
mpl::pair<double, mpl::false_>,
mpl::pair<float, mpl::false_>
> TAllowedTypesForMin;
template <class T>
T min (T p)
{
const bool allowed = mpl::at<TAllowedTypesForMin, T>::type::value;
if (allowed)
{
}
return p;
}
EDIT
with compile time check everything is simpler:
#include <boost/mpl/set.hpp>
#include <boost/mpl/assert.hpp>
using namespace boost;
template<class T> struct Point {};
typedef mpl::set<int, double, float> TAllowedTypesForMin;
template <class T>
T min (Point<T> p)
{
typedef mpl::has_key<TAllowedTypesForMin, T> allowedType;
BOOST_MPL_ASSERT_MSG( allowedType::value, NOT_SUPPORTED_TYPE, () );
// do something
return T();
}
int main()
{
min(Point<long>());
return 0;
}
As mentioned in the comment by Alexender C., are you sure a compile error wouldn't be more suitable?
template <typename T> struct GateKeeper;
// Func we want to call
template <typename S, typename T> void foo (T t);
// Warpper that checks the type and forwards the call
template <typename T> inline void foo (T t)
{
//
// This call will fail for unless a specialization of
// GateKeeper for `T` is defined with a member TYPE
foo< typename GateKeeper<T>::TYPE, T > ( t );
}
//
// This declaration "allows" the type int.
template <> struct GateKeeper<int> { typedef int TYPE; };
void bar ()
{
foo (0); // Compiles
foo (0.0); // Causes error in wrapping foo
}
A partial specialization can be used to allow any specialization of a given template:
// Some template type
template <typename T>
class TmplType
{
};
// This allows for specializations of TmplType
template <typename T> struct GateKeeper< TmplType<T> > { typedef int TYPE; };
void bar ()
{
TmplType<char> tt;
foo (tt); // Compiles
}
For each type that you wish to support you add a new specialization.
UPDATE: What is happening:
Note: I've changed the template parameter names from the original version to make things a bit clearer.
When the compiler sees a call to foo(x)
the only function that it can correctly specialize is foo<T>(T)
. This is because it cannot deduce all the template parameters for foo<S,T>(T)
.
The body of foo<T>(T)
forwards the call to the real function:
foo< typename GateKeeper<T>::TYPE, T > ( t );
(ASIDE: see here for description of when to use typename)
This is doing 2 things at once.
The first is to provide the 2 template parameters (S and T) which are required to call the other function.
The second is to use a member of GateKeeper<T>
as this other type. The type GateKeeper<T>
must be complete and have that member. It is this check that allows us to specify which types we want to allow and which we don't:
template <typename T> struct GateKeeper; // Incomplete
template <> struct GateKeeper<int> { typedef int TYPE; }; // Complete
As we have only provided a definition for GateKeeper<int>
and not for GateKeeper<double>
, the call to foo(0)
works correctly, and foo(0.0)
fails with a compile error.
To allow double
, we just need to add an explicit specialization for it:
template <> struct GateKeeper<double> { typedef int TYPE; }; // double now works.