2012-01-31 12 views
12

Jak wywnioskować statycznie, jeśli argument jest obiektem funkcji C++ (funktor)?Czy jest możliwa klasa funkcji klasy C++ dla klasy is_functor?

template <typename F> 
void test(F f) {} 

Próbowałem is_function<F>::value, ale to nie działa. Wydaje się również, że nie ma żadnej cechy, więc być może nie jest to możliwe. Wygląda na to, że szukam tylko określonej funkcji składowej, w tym przypadku operatora wywołania funkcji: F::operator().

+0

co powiesz na 'is_function :: value'? – Fiktik

+1

http://groups.google.com/group/comp.lang.c++.moderated/msg/e5fbc9305539f699 może Cię zainteresować. – pmr

+1

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

Odpowiedz

0
template<typename T, typename Sign>         
struct is_functor 
{                 
    typedef char yes[1];            
    typedef char no [2];            
    template <typename U, U> struct type_check;      
    template <typename _1> static yes &chk(type_check<Sign, &_1::operator()>*); 
    template <typename > static no &chk(...);      
    static bool const value = sizeof(chk<T>(nullptr)) == sizeof(yes);  
}; 

Zmieniono z this answer.

To może być używany jak ...

template<typename T> 
typename std::enable_if<is_functor<T, void(T::*)()>::value>::type func() 
{ 
} 
+0

"nazwa_typu decltype"? A twoje rozwiązanie nie działa, jeśli 'operator()' jest przeciążony. – kennytm

+0

Twoja definicja funktora jest niepełna. Standardowy funktor jest albo wskaźnikiem funkcji, albo obiektem z przeciążonym 'operator()'. –

+0

Opublikowałem inne rozwiązanie; @MaximYegorushkin, ale nowy nie zmienia się w związku z tym, hmmm – David

12

Jest możliwe, aby stworzyć taką cechę, z dwoma ograniczeniami:

  1. 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.
  2. 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

+0

Wow.Wow.Wow.Wow.Wow. – mark

0

Mimo to nie działa dla przeciążonych funkcji, dla wszystkich innych przypadkach (bezpłatne funkcje, klasy wykonawczych operator() i lambdas) to krótkie rozwiązanie działa w C++ 11:

template <typename T, typename Signature> 
struct is_callable: std::is_convertible<T,std::function<Signature>> { }; 

Uwaga: std::is_callable będzie dostępny w C++ 17.

Powiązane problemy