Во-первых, если вам требуется существование Init и Destroy, это означает, что код шаблона использует их где-то. Это означает, что их существование уже проверено компилятором, так как шаблон не скомпилируется, если тип не имеет этих методов.
Однако, если вы хотите проверить их, одним из способов может быть использование их адресов в некотором контексте времени компиляции, например,
template <class T>
class X
{
private:
template <unsigned N>
struct Number {};
Number<sizeof(&T::Init) + sizeof(&T::Destroy)> must_define_init_and_destroy();
};
struct A
{
bool Init();
void Destroy();
};
struct B {};
int main()
{
X<A>();
X<B>();
}
С Comeau вывод:
"ComeauTest.c", line 7: error: class "B" has no member "Init"
Number<sizeof(&T::Init) + sizeof(&T::Destroy)> must_define_init_and_destroy();
^
detected during instantiation of class "X<T> [with T=B]" at line 21
"ComeauTest.c", line 7: error: class "B" has no member "Destroy"
Number<sizeof(&T::Init) + sizeof(&T::Destroy)> must_define_init_and_destroy();
^
detected during instantiation of class "X<T> [with T=B]" at line 21
Однако, это ломается, если любой из требуемых методов перегружен, и естественно, что это все еще не проверяет, есть ли у этих методов подходящий прототип.
Например, вы ожидаете, что bool Init (int, int). Вы можете использовать static_cast для проверки точной подписи, но опять же это может наложить ненужные ограничения на тип. Например, что, если в каком-то классе вместо этого установлен bool Init (long, long)?
Так или иначе, эти усилия кажутся необходимыми только для того, чтобы сообщения об ошибках стали более очевидными. Однако я очень сомневаюсь, что любое сообщение, которое вы получите в противном случае без каких-либо концептуальных проверок (например, «нет подходящего метода Init, который вызывается с T = X при использовании здесь») - это плохо.