Создать такой трейт можно с двумя ограничениями:
- Для компилятора свободная функция — это нечто принципиально отличное от функтора класса, который перегружает
operator()
. Таким образом, мы должны рассматривать оба случая отдельно при реализации. Однако это не проблема для использования, мы можем скрыть эту деталь реализации от пользователя.
- Нам нужно знать сигнатуру функции, которую вы хотите вызвать. Обычно это не проблема, и у этого есть приятный побочный эффект, заключающийся в том, что наш трейт способен довольно естественно обрабатывать перегрузки.
Шаг 1. Бесплатные функции
Начнем со бесплатных функций, потому что их немного легче обнаружить. Наша задача состоит в том, чтобы, получив указатель на функцию, определить, соответствует ли сигнатура этого указателя функции сигнатуре, переданной в качестве второго аргумента шаблона. Чтобы иметь возможность сравнивать их, нам нужно либо получить представление о базовой сигнатуре функции, либо создать указатель функции на нашу сигнатуру. Я произвольно выбрал последнее:
// 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);
};
Шаг 3. Соединяем фрагменты
Мы почти закончили. Теперь нам нужно только решить, когда использовать нашу бесплатную функцию, а когда метафункции функтора класса. К счастью, 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
person
nijansen
schedule
04.09.2013
is_function<F::operator()>::value
? - person Fiktik   schedule 31.01.2012result_of
в SFINAE будет работать для идентификации любого вызываемого типа. Я немного удивлен, что, похоже, еще нет чертыstd::is_callable
. - person bames53   schedule 31.01.2012result_of
таким образом весь день. Кажется, что есть много ситуаций, когда GCC выдает ошибку в одной перегрузке, хотя существует другая допустимая перегрузка. - person user2023370   schedule 01.02.2012is_function<typename F::operator()>::value
также отклоняется:invalid template argument
. - person user2023370   schedule 01.02.2012F::operator()
. Все функции-члены имеют список аргументов, даже если этот список пуст. Вместо этого вы ищете существованиеF::operator()()
? Да, это возможно обнаружить. - person Howard Hinnant   schedule 01.02.2012