2013-03-13 29 views
26

È possibile scrivere un tratto di tipo, ad esempio is_callable<T> che indica se un oggetto ha un operator() definito? È facile se gli argomenti per l'operatore di chiamata sono noti in anticipo, ma non nel caso generale. Voglio che il carattere ritorni vero se e solo se è definito almeno un operatore di chiamata sovraccarico.scoprire se un oggetto C++ è richiamabile

This question è correlato e ha una buona risposta, ma non funziona su tutti i tipi (solo int convertibile). Inoltre, std::is_function funziona ma solo su funzioni reali e non su functor, sto cercando una soluzione più generale.

+2

È disponibile C++ 11? –

+0

[Questo] (http://stackoverflow.com/questions/9231247/how-to-write-the-best-posible-is-callable-trait-for-templated-operator) potrebbe essere rilevante –

+0

Hai una lista di possibili tipi di argomenti? Se è così, ciò lo rende sicuramente possibile. Non sono sicuro, tuttavia, di un sovraccarico generico. –

risposta

26

Penso che questo tratto faccia ciò che vuoi. Rileva operator() con qualsiasi tipo di firma, anche se è sovraccaricato e anche se è templatized:

template<typename T> 
struct is_callable { 
private: 
    typedef char(&yes)[1]; 
    typedef char(&no)[2]; 

    struct Fallback { void operator()(); }; 
    struct Derived : T, Fallback { }; 

    template<typename U, U> struct Check; 

    template<typename> 
    static yes test(...); 

    template<typename C> 
    static no test(Check<void (Fallback::*)(), &C::operator()>*); 

public: 
    static const bool value = sizeof(test<Derived>(0)) == sizeof(yes); 
}; 

Live demo.

Il principio si basa sulla Member Detector idiom. Così com'è, non riuscirà a compilare se si passa un tipo non di classe, ma che non dovrebbe essere difficile da risolvere, ho appena lasciato fuori per brevità. Puoi anche estenderlo per riportare true per le funzioni.

Ovviamente non fornisce alcuna informazione sulla firma (s) di operator(), ma credo che non è quello che hai chiesto, giusto?

EDIT per Klaim:

E 'abbastanza semplice per farlo funzionare (tornare false) con i tipi non-classe.Se si rinomina la classe di cui sopra per is_callable_impl, è possibile scrivere questo, ad esempio:

template<typename T> 
struct is_callable 
    : std::conditional< 
     std::is_class<T>::value, 
     is_callable_impl<T>, 
     std::false_type 
    >::type 
{ }; 
+0

+1, hai la vera soluzione. –

+0

Ottimo! Buono e codice breve, demo dal vivo e spiegazione. +1 per l'utilizzo di liveworkspace.org, grazie. – Antoine

+1

Bello ma questo non si compila se il tipo non è ereditabile, come int. Non sono sicuro di come modificarlo in modo che funzioni in questo caso (e dovrebbe restituire false suppongo). – Klaim

8

Ecco una possibile soluzione che utilizza C++ 11 che funziona senza la necessità di conoscere la firma dell'operatore richiesta di funtori, ma solo finché il functor non avere più di un sovraccarico di operator():

#include <type_traits> 

template<typename T, typename = void> 
struct is_callable : std::is_function<T> { }; 

template<typename T> 
struct is_callable<T, typename std::enable_if< 
    std::is_same<decltype(void(&T::operator())), void>::value 
    >::type> : std::true_type { }; 

Questo è come lo si dovrebbe utilizzare:

struct C 
{ 
    void operator()() { } 
}; 

struct NC { }; 

struct D 
{ 
    void operator()() { } 
    void operator() (int) { } 
}; 

int main() 
{ 
    static_assert(is_callable<C>::value, "Error"); 
    static_assert(is_callable<void()>::value, "Error"); 

    auto l = []() { }; 
    static_assert(is_callable<decltype(l)>::value, "Error"); 

    // Fires! (no operator()) 
    static_assert(is_callable<NC>::value, "Error"); 

    // Fires! (several overloads of operator()) 
    static_assert(is_callable<D>::value, "Error"); 
} 

Ecco un live example.

+0

Hum ... La limitazione degli operatori di sovraccarico è davvero limitante, ma la tua soluzione è molto compatta. – Antoine

2

Nota: presuppongono che il costruttore predefinito sia valido per il tipo di controllo. Non sono sicuro di come muoversi.

Quanto segue sembra funzionare se è callable con 0 argomenti. C'è qualcosa nella implementazione di is_function che potrebbe aiutare ad estendere questo per 1 o più oggetti chiamabili argomento ?:

template <typename T> 
struct is_callable { 
    // Types "yes" and "no" are guaranteed to have different sizes, 
    // specifically sizeof(yes) == 1 and sizeof(no) == 2. 
    typedef char yes[1]; 
    typedef char no[2]; 

    template <typename C> 
    static yes& test(decltype(C()())*); 

    template <typename> 
    static no& test(...); 

    // If the "sizeof" the result of calling test<T>(0) would be equal to the  sizeof(yes), 
    // the first overload worked and T has a nested type named foobar. 
    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

Se si conosce il tipo di argomento (anche se si tratta di un parametro di template), il seguente dovrebbe funzionare per 1 argomento, e immagino che si possa estendere abbastanza facilmente da lì:

template <typename T, typename T2> 
struct is_callable_1 { 
    // Types "yes" and "no" are guaranteed to have different sizes, 
    // specifically sizeof(yes) == 1 and sizeof(no) == 2. 
    typedef char yes[1]; 
    typedef char no[2]; 

    template <typename C> 
    static yes& test(decltype(C()(T2()))*); 

    template <typename, typename> 
    static no& test(...); 

    // If the "sizeof" the result of calling test<T>(0) would be equal to the  sizeof(yes), 
    // the first overload worked and T has a nested type named foobar. 
    static const bool value = sizeof(test<T>(0)) == sizeof(yes); 
}; 

Modifica here è una modifica che gestisce il caso in cui costruttore di default non è disponibile.

+2

Ri * Questi presuppongono che il costruttore predefinito sia valido per il tipo di controllo. Non sai bene come aggirare il problema. * Dai un'occhiata a ['std :: declval'] (http://en.cppreference.com/w/cpp/utility/declval). – jrok

+0

@jrok Grazie, non l'avevo ancora visto. nel pastebin che ho allegato, ho appena usato una struttura helper che aveva definito l'operatore di conversione necessario, ma avrei potuto sostituirlo con declval. – EHuhtala

7

Le risposte qui sono stati utili, ma sono venuto qui vogliono qualcosa che potrebbe anche individuare se qualcosa era richiamabile indipendentemente dal fatto che è capitato di essere un oggetto o una funzione classica. jrok's answer a questo aspetto del problema, purtroppo, non ha funzionato perché lo std::conditional valuta effettivamente i tipi di entrambe le braccia!

Quindi, ecco una soluzione:

// Note that std::is_function says that pointers to functions 
// and references to functions aren't functions, so we'll make our 
// own is_function_t that pulls off any pointer/reference first. 

template<typename T> 
using remove_ref_t = typename std::remove_reference<T>::type; 

template<typename T> 
using remove_refptr_t = typename std::remove_pointer<remove_ref_t<T>>::type; 

template<typename T> 
using is_function_t = typename std::is_function<remove_refptr_t<T>>::type; 

// We can't use std::conditional because it (apparently) must determine 
// the types of both arms of the condition, so we do it directly. 

// Non-objects are callable only if they are functions. 

template<bool isObject, typename T> 
struct is_callable_impl : public is_function_t<T> {}; 

// Objects are callable if they have an operator(). We use a method check 
// to find out. 

template<typename T> 
struct is_callable_impl<true, T> { 
private: 
    struct Fallback { void operator()(); }; 
    struct Derived : T, Fallback { }; 

    template<typename U, U> struct Check; 

    template<typename> 
    static std::true_type test(...); 

    template<typename C> 
    static std::false_type test(Check<void (Fallback::*)(), &C::operator()>*); 

public: 
    typedef decltype(test<Derived>(nullptr)) type; 
}; 


// Now we have our final version of is_callable_t. Again, we have to take 
// care with references because std::is_class says "No" if we give it a 
// reference to a class. 

template<typename T> 
using is_callable_t = 
    typename is_callable_impl<std::is_class<remove_ref_t<T>>::value, 
           remove_ref_t<T> >::type; 

Ma alla fine, per la mia applicazione, volevo sapere solo se si potrebbe dire f() (vale a dire, lo chiamano senza argomenti), in modo da Io invece sono andato con qualcosa di molto più semplice.

template <typename T> 
constexpr bool noarg_callable_impl(
    typename std::enable_if<bool(sizeof((std::declval<T>()(),0)))>::type*) 
{ 
    return true; 
} 

template<typename T> 
constexpr bool noarg_callable_impl(...) 
{ 
    return false; 
} 

template<typename T> 
constexpr bool is_noarg_callable() 
{ 
    return noarg_callable_impl<T>(nullptr); 
} 

In effetti, sono andato anche oltre. Sapevo che la funzione doveva restituire un int, quindi piuttosto che semplicemente controllare che potrei chiamare, ho controllato il tipo di ritorno, anche cambiando il enable_if a:

typename std::enable_if<std::is_convertible<decltype(std::declval<T>()()), 
               int>::value>::type*) 

Spero che questo aiuti qualcuno!

2

Ci sono già molte altre risposte, ovviamente, e sono utili, ma nessuna di esse sembra coprire ogni caso d'uso AFAICT. Prendendo a prestito da quelle risposte e this possible implementation of std::is_function, ho creato una versione che copre ogni possibile caso d'uso di cui potrei pensare. È un po 'lungo, ma molto completo (Demo).

template<typename T, typename U = void> 
struct is_callable 
{ 
    static bool const constexpr value = std::conditional_t< 
     std::is_class<std::remove_reference_t<T>>::value, 
     is_callable<std::remove_reference_t<T>, int>, std::false_type>::value; 
}; 

template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(*)(Args...), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(&)(Args...), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(*)(Args......), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(&)(Args......), U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)volatile, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const volatile, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)volatile, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const volatile, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)&, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)volatile&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const volatile&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)&, U> : std::true_type {}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)volatile&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const volatile&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)volatile&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args...)const volatile&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)volatile&&, U> : std::true_type{}; 
template<typename T, typename U, typename ...Args> 
struct is_callable<T(Args......)const volatile&&, U> : std::true_type{}; 

template<typename T> 
struct is_callable<T, int> 
{ 
private: 
    using YesType = char(&)[1]; 
    using NoType = char(&)[2]; 

    struct Fallback { void operator()(); }; 

    struct Derived : T, Fallback {}; 

    template<typename U, U> 
    struct Check; 

    template<typename> 
    static YesType Test(...); 

    template<typename C> 
    static NoType Test(Check<void (Fallback::*)(), &C::operator()>*); 

public: 
    static bool const constexpr value = sizeof(Test<Derived>(0)) == sizeof(YesType); 
}; 

Ciò funziona correttamente con i tipi non-classe (restituisce false, ovviamente), tipi di funzione (< T() >), i tipi di puntatore a funzione, tipi di riferimento funzione, tipi di classe funtore, legare espressioni, tipi lambda , ecc. Funziona correttamente anche se il costruttore della classe è privato e/o non predefinito, e anche se operator() è sovraccarico. Ciò restituisce false per i puntatori di funzione dei membri in base alla progettazione perché non sono chiamabili, ma è possibile utilizzare bind per creare un'espressione chiamabile.

+0

Nota: Visual Studio 2015 sembra soffocare sui sovraccarichi della "funzione variadica" ("Args ......"), e confesserò che non sono del tutto sicuro di cosa significhi o perché abbia quella sintassi . –

+0

In realtà, ho fatto ulteriori test e credo di aver capito. Se una funzione come 'template void bar (Args && ... args, ...);' sono stati definiti (anche se dovresti mescolare un modello di funzione variadic con il variadic non sicuro '... 'è oltre me!), quindi' is_callable )> 'si riferirà al sovraccarico di' Args ...... '. Poiché il pacchetto di parametri 'Args' può essere vuoto, non puoi digitare' Args ..., ... 'come elenco dei parametri, e se non è vuoto allora automaticamente inserirà la virgola necessaria per te (eccetto , a quanto pare, in Visual Studio 2015). –

0

Ecco un'altra implementazione.

Utilizza il modello std::is_function per funzioni gratuite.

Per le classi, utilizza qualcosa di simile allo Member Detector Idiom. Se T dispone di un operatore di chiamata, callable_2 conterrà più di uno operator(). Ciò causerà l'eliminazione della prima funzione can_call (tramite SFINAE) a causa dell'errore di ambiguità in decltype(&callable_2<T>::operator()) e la seconda funzione can_call restituirà true. Se T non dispone di un operatore di chiamata, verrà utilizzata la prima funzione can_call (a causa delle regole di risoluzione del sovraccarico).

namespace impl 
{ 
struct callable_1 { void operator()(); }; 
template<typename T> struct callable_2 : T, callable_1 { }; 

template<typename T> 
static constexpr bool can_call(decltype(&callable_2<T>::operator())*) noexcept { return false; } 

template<typename> 
static constexpr bool can_call(...) noexcept { return true; } 

template<bool is_class, typename T> 
struct is_callable : public std::is_function<T> { }; 

template<typename T> struct is_callable<false, T*> : public is_callable<false, T> { }; 
template<typename T> struct is_callable<false, T* const> : public is_callable<false, T> { }; 
template<typename T> struct is_callable<false, T* volatile> : public is_callable<false, T> { }; 
template<typename T> struct is_callable<false, T* const volatile> : public is_callable<false, T> { }; 

template<typename T> 
struct is_callable<true, T> : public std::integral_constant<bool, can_call<T>(0)> { }; 
} 

template<typename T> 
using is_callable = impl::is_callable<std::is_class<std::remove_reference_t<T>>::value, 
            std::remove_reference_t<T>>; 
Problemi correlati