Можно создать такую черту с двумя ограничениями:
- Для компилятора свободная функция принципиально отличается от функтора класса, который перегружает
operator()
. Таким образом, мы должны рассматривать оба случая отдельно при реализации. Это не проблема для использования, хотя мы можем скрыть детали реализации от пользователя.
- Нам нужно знать сигнатуру функции, которую вы хотите вызвать. Обычно это не проблема, и у нее есть приятный побочный эффект - наша черта способна довольно естественным образом справляться с перегрузками.
Шаг первый: бесплатные функции
Давайте начнем со бесплатных функций, потому что их немного легче обнаружить. Наша задача, когда передается указатель функции, определить, совпадает ли подпись этого указателя функции с подписью, переданной в качестве второго аргумента шаблона. Чтобы иметь возможность сравнивать их, нам нужно либо понять основную сигнатуру функции, либо создать указатель на функцию нашей сигнатуры. Я произвольно выбрал последнее:
// build R (*)(Args...) from R (Args...)
// compile error if signature is not a valid function signature
template <typename, typename>
struct build_free_function;
template <typename F, typename R, typename ... Args>
struct build_free_function<F, R (Args...)>
{ using type = R (*)(Args...); };
Теперь все, что осталось сделать, это сравнить, и мы закончили с бесплатной функциональной частью:
// determine whether a free function pointer F has signature S
template <typename F, typename S>
struct is_function_with_signature
{
// check whether F and the function pointer of S are of the same
// type
static bool constexpr value = std::is_same<
F, typename build_free_function<F, S>::type
>::value;
};
Шаг второй: функторы класса
Этот немного более сложный. С помощью SFINAE мы можем легко определить, определяет ли класс operator()
:
template <typename T>
struct defines_functor_operator
{
typedef char (& yes)[1];
typedef char (& no)[2];
// we need a template here to enable SFINAE
template <typename U>
static yes deduce(char (*)[sizeof(&U::operator())]);
// fallback
template <typename> static no deduce(...);
static bool constexpr value = sizeof(deduce<T>(0)) == sizeof(yes);
};
но это не говорит нам, существует ли кто-то для нашей желаемой сигнатуры функции! К счастью, здесь можно использовать хитрость: указатели являются допустимыми параметрами шаблона. Таким образом, мы можем сначала использовать указатель на функцию-член нашей желаемой подписи и проверить, относится ли &T::operator()
к этому типу:
template <typename T, T> struct check;
Теперь check<void (C::*)() const, &C::operator()>
будет допустимым экземпляром шаблона, только если C
действительно имеет void C::operator()() const
. Но для этого нам сначала нужно объединить C
и подпись для указателя на функцию-член. Как мы уже видели, нам нужно беспокоиться о двух дополнительных случаях, о которых нам не нужно было заботиться о свободных функциях: const
и volatile
функции. Кроме того, это почти то же самое:
// build R (C::*)(Args...) from R (Args...)
// R (C::*)(Args...) const from R (Args...) const
// R (C::*)(Args...) volatile from R (Args...) volatile
// compile error if signature is not a valid member function signature
template <typename, typename>
struct build_class_function;
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...)>
{ using type = R (C::*)(Args...); };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) const>
{ using type = R (C::*)(Args...) const; };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) volatile>
{ using type = R (C::*)(Args...) volatile; };
Объединяя это и наши выводы относительно вспомогательной структуры check
, мы получаем нашу мета-функцию проверки для объектов функторов:
// determine whether a class C has an operator() with signature S
template <typename C, typename S>
struct is_functor_with_signature
{
typedef char (& yes)[1];
typedef char (& no)[2];
// helper struct to determine that C::operator() does indeed have
// the desired signature; &C::operator() is only of type
// R (C::*)(Args...) if this is true
template <typename T, T> struct check;
// T is needed to enable SFINAE
template <typename T> static yes deduce(check<
typename build_class_function<C, S>::type, &T::operator()> *);
// fallback if check helper could not be built
template <typename> static no deduce(...);
static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
};
Шаг третий: Соединение частей
Мы почти закончили. Теперь нам нужно только решить, когда использовать нашу свободную функцию, а когда метафункции функтора класса. К счастью, C ++ 11 предоставляет нам черту std::is_class
, которую мы можем использовать для этого. Поэтому все, что нам нужно сделать, это специализироваться на булевом параметре:
// C is a class, delegate to is_functor_with_signature
template <typename C, typename S, bool>
struct is_callable_impl
: std::integral_constant<
bool, is_functor_with_signature<C, S>::value
>
{};
// F is not a class, delegate to is_function_with_signature
template <typename F, typename S>
struct is_callable_impl<F, S, false>
: std::integral_constant<
bool, is_function_with_signature<F, S>::value
>
{};
Итак, мы можем наконец добавить последний кусочек головоломки, являясь нашей реальной чертой is_callable
:
// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
: is_callable_impl<
Callable, Signature,
std::is_class<Callable>::value
>
{};
Теперь мы очистим наш код, поместим детали реализации в анонимные пространства имен, чтобы они не были доступны за пределами нашего файла, и получили хороший is_callable.hpp
для использования в нашем проекте.
Полный код
namespace // implementation detail
{
// build R (*)(Args...) from R (Args...)
// compile error if signature is not a valid function signature
template <typename, typename>
struct build_free_function;
template <typename F, typename R, typename ... Args>
struct build_free_function<F, R (Args...)>
{ using type = R (*)(Args...); };
// build R (C::*)(Args...) from R (Args...)
// R (C::*)(Args...) const from R (Args...) const
// R (C::*)(Args...) volatile from R (Args...) volatile
// compile error if signature is not a valid member function signature
template <typename, typename>
struct build_class_function;
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...)>
{ using type = R (C::*)(Args...); };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) const>
{ using type = R (C::*)(Args...) const; };
template <typename C, typename R, typename ... Args>
struct build_class_function<C, R (Args...) volatile>
{ using type = R (C::*)(Args...) volatile; };
// determine whether a class C has an operator() with signature S
template <typename C, typename S>
struct is_functor_with_signature
{
typedef char (& yes)[1];
typedef char (& no)[2];
// helper struct to determine that C::operator() does indeed have
// the desired signature; &C::operator() is only of type
// R (C::*)(Args...) if this is true
template <typename T, T> struct check;
// T is needed to enable SFINAE
template <typename T> static yes deduce(check<
typename build_class_function<C, S>::type, &T::operator()> *);
// fallback if check helper could not be built
template <typename> static no deduce(...);
static bool constexpr value = sizeof(deduce<C>(0)) == sizeof(yes);
};
// determine whether a free function pointer F has signature S
template <typename F, typename S>
struct is_function_with_signature
{
// check whether F and the function pointer of S are of the same
// type
static bool constexpr value = std::is_same<
F, typename build_free_function<F, S>::type
>::value;
};
// C is a class, delegate to is_functor_with_signature
template <typename C, typename S, bool>
struct is_callable_impl
: std::integral_constant<
bool, is_functor_with_signature<C, S>::value
>
{};
// F is not a class, delegate to is_function_with_signature
template <typename F, typename S>
struct is_callable_impl<F, S, false>
: std::integral_constant<
bool, is_function_with_signature<F, S>::value
>
{};
}
// Determine whether type Callable is callable with signature Signature.
// Compliant with functors, i.e. classes that declare operator(); and free
// function pointers: R (*)(Args...), but not R (Args...)!
template <typename Callable, typename Signature>
struct is_callable
: is_callable_impl<
Callable, Signature,
std::is_class<Callable>::value
>
{};
Идеальный пример с некоторыми тестами
http://ideone.com/7PWdiv