I'm going to describe a solution with c++11, and then explain how to implement it in c++98.
In c++11 the header <type_traits>
includes type functions and type predicates. This makes enforcing constraints more convenient.
std::is_same<T1, T2>::value
is true if T1
is the same type as T2
and false otherwise.
typename std::enable_if< bool, T >::type
is a well defined type T
if bool is true and ill-defined otherwise.
When the compiler looks for candidate template functions and methods, it doesn't throw an error if an attempted specialization fails. It simply throws out that candidate. Which means the following code will remove the ambiguity:
template <typename TNum, typename T2>
typename std::enable_if< (!std::is_same<Matrix<TNum>, T2>::value),
Matrix<TNum> >::type operator*(const T2& lhs, Matrix<TNum> rhs);
Only declarations are considered when making this decision. The above logic is semantically reasonably, but an eyesore to read. So c++11 supports template aliases, and constexpr functions.
template<bool B, typename T = void>
using Enable_if = typename std::enable_if<B, T>::type;
template<typename T1, typename T2>
constexpr bool Is_same(){
return std::is_same<T1, T2>::value;
}
The above then becomes:
template <typename TNum, typename T2>
Enable_if<( !Is_same<Matrix<TNum>, T2>() ),
Matrix<TNum> > operator*(const T2& lhs, Matrix<TNum> rhs);
concepts will provide tools to make this more convenient.
Now if you don't have c++11, you don't get the eye-candy. But, Boost provides the same functions. Suppose you don't have either, implementing them isn't terrible.
Compile time functions depend on multiple language rules, which makes them difficult to understand. We'll consider enable_if
first. We want typename enable_if<true, T>::type
to be well defined but, typename enable_if<false, T>::type
to be nonsense. We use specialization:
template<bool B, typename T = void>
struct enable_if {
typedef T type;
};
template<typename T>
struct enable_if<false, T> {};
notice false handles exactly half of relevant cases. It's worthwhile to chew on the above.
In order to implement is_same
, we need a notion of true or false at compile time. We can guarantee this with static const variables. We want is_same
to have a compile time true value, whenever its template arguments are the same. The specialization system rules handle this directly.
template<typename, typename>
struct is_same{
static const bool value = false;
};
template<typename T>
struct is_same<T, T>{
static const bool value = true;
};
This should do what you want. Note you could abstract one step further and make another pair of structs.
struct false_type {
static const bool value = false;
};
struct true_type {
static const bool value = true;
};
then is_same
becomes:
template<typename, typename>
struct is_same : false_type {};
template<typename T>
struct is_same<T, T> : true_type {};
which makes it look more like a function.
I prefer this to the category solution, because it's easier to separate the metaprogram into a header file. You can then reuse the logic elsewhere. Still, if you aren't using c++11 or even boost, making necessary compile time functions can be a headache.
If using complex( or any simple redesign ) satisfies your current and future requirements - prefer that. Otherwise I think this solution is reasonably future proof.