Это не так просто, и в C ++ 03 это вообще невозможно.Например, если вы используете int*
и int*
для op<<
, вы получите серьезную ошибку во время компиляции.Таким образом, для типов, не относящихся к классам, вам необходимо отфильтровать типы, запрещенные стандартом.
Для op+
Однажды я написал такую вещь для пинков.Обратите внимание, что я использую заголовки C
, потому что мне нужно было протестировать код и с компилятором clang
, который в то время не поддерживал мои заголовки C ++:
#include <stddef.h>
#include <stdio.h>
namespace detail {
struct any {
template<typename T> any(T const&);
};
struct tag { char c[2]; };
int operator,(detail::tag, detail::tag);
template<typename T> void operator,(detail::tag, T const&);
char operator,(int, detail::tag);
}
namespace fallback {
detail::tag operator+(detail::any const&, detail::any const&);
}
namespace detail {
template<typename T>
struct is_class {
typedef char yes[1];
typedef char no[2];
template<typename U>
static yes &check(int U::*);
template<typename U>
static no &check(...);
static bool const value = sizeof check<T>(0) == 1;
};
template<typename T>
struct is_pointer { typedef T pointee; static bool const value = false; };
template<typename T>
struct is_pointer<T*> { typedef T pointee; static bool const value = true; };
template<typename T, typename U>
struct is_same {
static bool const value = false;
};
template<typename T>
struct is_same<T, T> {
static bool const value = true;
};
template<typename T>
struct is_incomplete_array {
static bool const value = false;
};
template<typename T>
struct is_incomplete_array<T[]> {
static bool const value = true;
};
template<typename T>
struct is_reference {
typedef T referee;
static bool const value = false;
};
template<typename T>
struct is_reference<T&> {
typedef T referee;
static bool const value = true;
};
// is_fn checks whether T is a function type
template<typename T>
struct is_fn {
typedef char yes[1];
typedef char no[2];
template<typename U>
static no &check(U(*)[1]);
template<typename U>
static yes &check(...);
// T not void, not class-type, not U[], U& and T[] invalid
// => T is function type
static bool const value =
!is_same<T const volatile, void>::value &&
!is_class<T>::value &&
!is_incomplete_array<T>::value &&
!is_reference<T>::value &&
(sizeof check<T>(0) == 1);
};
template<typename T, bool = is_fn<T>::value>
struct mod_ty {
typedef T type;
};
template<typename T>
struct mod_ty<T, true> {
typedef T *type;
};
template<typename T>
struct mod_ty<T[], false> {
typedef T *type;
};
template<typename T, size_t N>
struct mod_ty<T[N], false> {
typedef T *type;
};
// Standard says about built-in +:
//
// For addition, either both operands shall have arithmetic or enumeration type,
// or one operand shall be a pointer to a completely defined object type and
// the other shall have integral or enumeration type.
template<typename T> struct Ty; // one particular type
struct P; // pointer
struct Nc; // anything nonclass
struct A; // anything
struct Fn; // function pointer
// matches category to type
template<typename C, typename T,
bool = is_pointer<T>::value,
bool = !is_class<T>::value,
bool = is_fn<typename is_pointer<T>::pointee>::value>
struct match {
static bool const value = false;
};
// one particular type
template<typename T, bool P, bool Nc, bool Fn>
struct match<Ty<T const volatile>, T, P, Nc, Fn> {
static bool const value = false;
};
// pointer
template<typename T, bool F>
struct match<P, T, true, true, F> {
static bool const value = true;
};
// anything nonclass
template<typename T, bool P, bool Fn>
struct match<Nc, T, P, true, Fn> {
static bool const value = true;
};
// anything
template<typename T, bool P, bool Nc, bool Fn>
struct match<A, T, P, Nc, Fn> {
static bool const value = true;
};
// function pointer
template<typename T>
struct match<Fn, T, true, true, true> {
static bool const value = true;
};
// one invalid combination
template<typename A, typename B>
struct inv;
// a list of invalid combinations, terminated by B = void
template<typename A, typename B>
struct invs;
// T[] <=> T[N] => T*
// void() => void(*)()
// T& => T
// trying to find all invalid combinations
// for built-in op+
typedef
invs<
inv<Ty<float const volatile>, P>,
invs<
inv<Ty<double const volatile>, P>,
invs<
inv<Ty<long double const volatile>, P>,
invs<
inv<Ty<void * const volatile>, Nc>,
invs<
inv<Ty<void const* const volatile>, Nc>,
invs<
inv<Ty<void volatile* const volatile>, Nc>,
invs<
inv<Ty<void const volatile* const volatile>, Nc>,
invs<
inv<Fn, Nc>,
invs<
inv<Ty<void const volatile>, A>,
invs<
inv<P, P>,
void
> > > > > > > > > > invalid_list;
// match condition: only when ECond<true> is passed by specialization,
// then it will be selected.
template<bool> struct ECond;
template<typename L, typename T, typename U, typename = ECond<true> >
struct found_impl;
// this one will first modify the input types to be plain pointers
// instead of array or function types.
template<typename L, typename T, typename U>
struct found : found_impl<L,
typename mod_ty<
typename is_reference<T>::referee>::type,
typename mod_ty<
typename is_reference<U>::referee>::type>
{ };
// match was found.
template<typename F, typename B, typename R, typename T, typename U>
struct found_impl<invs<inv<F, B>, R>, T, U,
ECond<(match<F, T>::value && match<B, U>::value) ||
(match<B, T>::value && match<F, U>::value)> > {
static bool const value = true;
};
// recurse (notice this is less specialized than the previous specialization)
template<typename H, typename R, typename T, typename U, typename Ec>
struct found_impl< invs<H, R>, T, U, Ec > : found_impl<R, T, U> {
};
// we hit the end and found nothing
template<typename T, typename U, typename Ec>
struct found_impl< void, T, U, Ec > {
static bool const value = false;
};
using namespace fallback;
template<typename T, typename U,
bool found_invalid = found<invalid_list, T, U>::value>
struct is_addable {
static T t;
static U u;
static bool const value = sizeof (detail::tag(), (t+u), detail::tag()) != 1;
};
template<typename T, typename U>
struct is_addable<T, U, true> {
static bool const value = false;
};
}
template<typename T, typename U> struct is_addable {
static bool const value = detail::is_addable<T, U>::value;
};
Конечно,очень важно делать тесты потом
// this one can be added
struct test {
test operator+(test) { return(*this); }
};
// this one cannot be added
struct nono { };
// this fails because of an ambiguity, because there is a comma
// operator taking a variable parameter on its left hand side.
struct fails { fails operator+(fails); };
template<typename T>
void operator,(T const&, fails);
int main() {
printf("%d\n", is_addable<test, test>::value);
printf("%d\n", is_addable<int, float>::value);
printf("%d\n", is_addable<nono, nono>::value);
printf("%d\n", is_addable<int*, int>::value);
printf("%d\n", is_addable<int[1], int>::value);
printf("%d\n", is_addable<int[1], float[2]>::value);
printf("%d\n", is_addable<int*, float*>::value);
printf("%d\n", is_addable<void*, float>::value);
printf("%d\n", is_addable<void, int>::value);
printf("%d\n", is_addable<void(), int>::value);
printf("%d\n", is_addable<int, void(**)()>::value);
printf("%d\n", is_addable<float*&, int*&>::value);
}