Попробуйте это:
template <typename T, typename = void> struct has_operator {
enum { value = 0 };
};
// thanks super for great suggestion!
template <typename T> struct has_operator<T, std::void_t<decltype(std::declval<T>()())>> {
enum { value = 1 };
};
template<class T, typename = std::enable_if<has_operator<T>::value>> struct Caller: T
{
using T::operator();
};
Это работает по принципу SFINAE - некоторые ошибки в реализации шаблона вместо сбоя всей компиляции просто заставят компилятор игнорировать данную реализацию. Итак, сначала мы определяем has_operator
с value = 0
, что является нашим «значением по умолчанию». Затем мы делаем специализацию шаблона для типа T. Теперь мы хотим, чтобы эта специализация выбиралась только тогда, когда существует T::operator()
. Поэтому мы добавляем второй аргумент шаблона и устанавливаем его значение по умолчанию decltype(&T::operator())
и value = 1
. Мы не заботимся о реальном типе здесь, мы заботимся о том, что если существует T::operator()
, это скомпилируется просто отлично. И компилятор выберет эту специализацию для этого T
. Когда он не существует - компилятор проигнорирует эту специализацию и выберет "по умолчанию" has_operator
, который имеет value = 0
.
Так что теперь у нас есть struct has_operator
, который - при использовании так: has_operator<T>::value
будет выдавать постоянное значение 0, когда T не имеет operator ()
(с любыми аргументами, обратите внимание) и значение 1, когда имеет , Вы можете использовать его с std::enable_if
(что, кстати, работает примерно так же).
Список вещей, которые могут быть применены с помощью этой техники, довольно длинный - может использоваться почти все, что может создавать или нарушать компиляцию.