Jest możliwe, aby stworzyć taką cechę, z dwoma ograniczeniami:
- dla kompilatora, darmowa funkcja jest coś fundamentalnie różni się od funktor klasy, który przeciąży
operator()
. W związku z tym musimy traktować oba przypadki oddzielnie podczas wdrażania. Nie jest to jednak problem z używaniem, ale możemy ukryć ten szczegół implementacji od użytkownika.
- Musimy znać sygnaturę funkcji, którą chcesz wywołać. Zwykle nie stanowi to problemu i ma ładny efekt uboczny, że nasza cecha jest w stanie poradzić sobie z ładowaniem dość natywnie.
Krok pierwszy: Wolny funkcje
Zacznijmy wolnych funkcji, ponieważ są nieco łatwiejsze do wykrycia. Naszym zadaniem jest podanie wskaźnika funkcji w celu ustalenia, czy sygnatura wskaźnika tej funkcji jest zgodna z sygnaturą przekazaną jako drugi argument szablonu. Aby móc je porównać, musimy albo poznać sygnaturę funkcji podstawowej, albo utworzyć wskaźnik funkcji naszego podpisu. I samowolnie wybrał to drugie:
// 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...); };
Teraz wszystko, co pozostało do zrobienia jest, aby porównać i skończymy z wolnej części funkcji:
// 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;
};
Krok drugi: funktory Class
Ten jest nieco bardziej zaangażowany. Możemy łatwo wykryć z SFINAE czy klasa definiuje 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);
};
ale to nie mówi nam, czy taka istnieje dla naszego podpisu żądanej funkcji! Na szczęście możemy tutaj użyć sztuczki: wskaźniki są poprawnymi parametrami szablonu. W ten sposób możemy najpierw użyć funkcji wskaźnik członek naszego pożądanego podpisu i sprawdzić, czy &T::operator()
jest tego typu:
template <typename T, T> struct check;
Teraz check<void (C::*)() const, &C::operator()>
będzie tylko ważne instancji szablonu jeśli C
rzeczywiście mają void C::operator()() const
. Ale aby to zrobić, musimy najpierw połączyć C
i podpis z wskaźnikiem funkcji członka. Jak już widzieliśmy, musimy się martwić o dwa dodatkowe przypadki, w których nie musieliśmy przejmować się funkcjami darmowymi: const
i volatile
.Poza tym, że jest to niemal tak samo:
// 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; };
Umieszczenie że i nasze odkrycia dotyczące struct check
pomocnika razem otrzymujemy naszą metafunkcji wyboru obiektów funktorów:
// 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);
};
Krok trzeci: Umieszczenie elementów razem:
Już prawie skończyliśmy. Teraz musimy tylko zdecydować, kiedy korzystać z naszej bezpłatnej funkcji, a kiedy metafunctions funktora klasy. Na szczęście C++ 11 dostarcza nam cechy std::is_class
, którą możemy wykorzystać w tym celu. Więc wszystko co musimy zrobić, to specjalizuje się w logiczną parametru:
// 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
>
{};
więc wreszcie możemy dodać ostatni element układanki, jest nasz rzeczywisty is_callable
cecha:
// 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
>
{};
Teraz musimy oczyścić nasze kodu, umieść szczegóły implementacji w anonimowych przestrzeniach nazw, aby nie były dostępne poza naszym plikiem i mają ładne is_callable.hpp
do wykorzystania w naszym projekcie.
Pełny kod
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 przykład z niektórych badań
http://ideone.com/7PWdiv
co powiesz na 'is_function :: value'? –
Fiktik
http://groups.google.com/group/comp.lang.c++.moderated/msg/e5fbc9305539f699 może Cię zainteresować. – pmr
Czy chcesz przetestować funktory lub dowolny obiekt do wywołania? Wygląda na to, że użycie funkcji 'result_of' w SFINAE będzie działało w celu zidentyfikowania dowolnego typu wywoływalnego. Jestem nieco zaskoczony, że nie ma już żadnej cechy "std :: is_callable". – bames53