Можно создать такую черту, с двумя ограничениями:
- Для компилятора, свободная функция что-то принципиально отличается от функтор класса, который перегружает
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
хелперов-структуру вместе, мы получаем наш чек metafunction для объектов функторных:
// 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
>
{};
Ideone пример с некоторых тестов
http://ideone.com/7PWdiv
как насчет 'is_function :: value'? –
Fiktik
http://groups.google.com/group/comp.lang.c++.moderated/msg/e5fbc9305539f699 может заинтересовать вас. – pmr
Вы просто хотите проверить для функторов или для любого вызываемого объекта? Похоже, что использование SFINAE признака 'result_of' будет работать, чтобы идентифицировать любой вызываемый тип. Я немного удивлен тем, что уже не существует какой-либо «std :: is_callable». – bames53