2014-10-14 18 views
14

Więc jestem bardzo zaznajomiony z paradygmatem testowania, jeśli istnieje funkcja członka. Obecnie ten kod działa:Testowanie, czy funkcja członkowska istnieje przy użyciu variadics

#include <iostream> 
#include <type_traits> 

struct has_mem_func_foo_impl { 
    template <typename U, U> 
    struct chk { }; 

    template <typename Class, typename Arg> 
    static std::true_type has_foo(chk<void(Class::*)(Arg), &Class::foo>*); 

    template <typename, typename> 
    static std::false_type has_foo(...); 
}; 

template <typename Class, typename Arg> 
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg>(nullptr)) { }; 


struct bar { 
    void foo(int) { } 
}; 

int main() { 
    static_assert(has_mem_func_foo<bar, int>::value, "bar has foo(int)"); 
} 

niestety jeśli zrobię lekką korektę:

#include <iostream> 
#include <type_traits> 

struct has_mem_func_foo_impl { 
    template <typename U, U> 
    struct chk { }; 

    template <typename Class, typename... Arg> 
    static std::true_type has_foo(chk<void(Class::*)(Arg...), &Class::foo>*); 

    template <typename, typename...> 
    static std::false_type has_foo(...); 
}; 

template <typename Class, typename... Arg> 
struct has_mem_func_foo : decltype(has_mem_func_foo_impl::template has_foo<Class,Arg...>(nullptr)) { }; 


struct bar { 
    void foo(int) { } 
}; 

int main() { 
    static_assert(has_mem_func_foo<bar, int>::value, "bar has foo(int)"); 
} 

mój statyczny twierdzenie zawiedzie. Miałem wrażenie, że pakiety parametrów szablonów variadic traktowane są tak samo, gdy są rozszerzane na swoje miejsca. Zarówno gcc, jak i clang dają nieudane asercje statyczne.

Prawdziwym źródłem mojego pytania jest zatem, czy to standardowe zachowanie? Zawodzi również w przypadku testowania obecności funkcji członów z szablonem variadic.

+0

Problem polega na tym, że kompilator nie może zagwarantować, że więcej "... Arg" nie powinno zostać wydedukowane. Przekazujesz w pierwszym 'Arg', ale musisz również sprawdzić przeciwko każdemu dodatkowemu' Arg'. Hmm. Myślę, że mam pomysł, czy to prawda. – Yakk

+0

Rozwiązaniem jest przeniesienie 'typename ... Arg' na parametry szablonu' has_mem_func_foo_impl', [jak tutaj] (http://coliru.stacked-crooked.com/a/015c3128ced5882d) –

+0

Zdecydowanie mogę zobaczyć, jak to się może stać , ale w tym przypadku przekazujemy pakiet parametrów jawnie 'int'. – cdacamara

Odpowiedz

8

Problem polega na tym, że Arg... przekazanie int nie wystarczy. Byłoby ważne, aby kompilator dodawał nowe argumenty na końcu.

Dedukowanie, co dodać na końcu z nullptr_t nie jest możliwe, więc kompilator mówi "Poddaję się, a nie ten przypadek".

But we don't need to have Arg... in a deducable context for your trick to work:

#include <iostream> 
#include <type_traits> 

template<class Sig> 
struct has_mem_func_foo_impl; 

template<class R, class...Args> 
struct has_mem_func_foo_impl<R(Args...)> { 
    template <typename U, U> 
    struct chk { }; 

    template <typename Class> 
    static constexpr std::true_type has_foo(chk<R(Class::*)(Args...), &Class::foo>*) { return {}; } 

    template <typename> 
    static constexpr std::false_type has_foo(...) { return {}; } 
}; 

template <typename Class, typename Sig> 
struct has_mem_func_foo : 
    decltype(has_mem_func_foo_impl<Sig>::template has_foo<Class>(nullptr)) 
{}; 

struct bar { 
    void foo(int) { } 
}; 


int main() { 
    static_assert(has_mem_func_foo<bar, void(int)>::value, "bar has foo(int)"); 
} 

przesuwamy Args... do samej klasie, a następnie przekazać tylko w typie do funkcji. To blokuje odliczanie, co powoduje, że konwersja nullptr do wskaźnika funkcji członka jest możliwa, a rzeczy znów działają.

Wprowadziłem również poprawioną składnię opartą na sygnaturach, co oznacza również, że obsługuje ona dopasowanie typu powrotu.

Pamiętaj, że możesz zadawać niewłaściwe pytania. Pytasz, czy istnieje funkcja składowa z konkretnym sygnaturą: często chcesz wiedzieć, czy istnieje funkcja składowa, która jest możliwa do zaakceptowania z określonym zbiorem argumentów, z typem zwracanym zgodnym z twoją wartością zwracaną.

namespace details { 
    template<class T, class Sig, class=void> 
    struct has_foo:std::false_type{}; 

    template<class T, class R, class... Args> 
    struct has_foo<T, R(Args...), 
    typename std::enable_if< 
     std::is_convertible< 
     decltype(std::declval<T>().foo(std::declval<Args>()...)), 
     R 
     >::value 
     || std::is_same<R, void>::value // all return types are compatible with void 
     // and, due to SFINAE, we can invoke T.foo(Args...) (otherwise previous clause fails) 
    >::type 
    >:std::true_type{}; 
} 
template<class T, class Sig> 
using has_foo = std::integral_constant<bool, details::has_foo<T, Sig>::value>; 

który próbuje wywołać T.foo(int) i sprawdza, czy wartość zwracana jest kompatybilne.

Dla zabawy zrobiłem typ has_foo w rzeczywistości być true_type lub false_type, nie odziedziczone z. Mógłbym po prostu:

template<class T, class Sig> 
using has_foo = details::has_foo<T, Sig>; 

jeśli nie chciałem tej dodatkowej funkcji.

+1

Nie jestem pewien czy podążam i jeśli nie masz nic przeciwko tłumaczeniu ... dlaczego to tutaj musi coś zgadywać? 'Args ...' jest tutaj wyraźnie "int", nie?[Działa to] (http://coliru.stacked-crooked.com/a/d98b218acd9e0065) z, jak sądzę, dokładnie taką samą ilością informacji, które są dostępne dla kompilatora. – jrok

+0

Tak jak wyjaśniłem @Yakk, przekazałem tylko pierwszy element pakietu parametrów do kontekstu funkcji wywnioskowanej, to jest jak częściowa specjalizacja, a kompilator nie może zagwarantować, że nie ma więcej rzeczy do wyprowadzenia w tym przypadku, lub późniejszego specjalizacja (do porównania z szablonami obiektów). – cdacamara

+0

@jrok nie jestem pewien. Może parametry domyślne nie wywołują odliczeń w ten sam sposób? Wyjaśniłem, co było mylące kompilator z komunikatów o błędach, gdy usunąłem przeciążenie 'false_type'. – Yakk

Powiązane problemy