2016-11-29 22 views
6

Próbuję zaimplementować funkcję meta szablonu C++, która określa, czy typ jest wywoływalny z argumentów wejściowych metody.Funkcja meta C++, która określa, czy typ można wywoływać dla podanych argumentów.

czyli dla funkcji void foo(double, double) funkcja meta wróci true dla callable_t<foo, double, double>, true dla callable_t<foo, int, int> (ze względu na kompilator robi niejawna cast) i false do niczego innego, takiego jak niewłaściwej liczby argumentów callable_t<foo, double>.

Moja próba jest następująca, jednak nie działa dla żadnej funkcji, która zwraca coś innego niż nieważne i nie mogę tego naprawić.

Jestem nowicjuszem w przeprogramowywaniu szablonów, więc każda pomoc będzie mile widziana.

#include <iostream> 
#include <type_traits> 
#include <utility> 
#include <functional> 

namespace impl 
{ 

template <typename...> 
struct callable_args 
{ 
}; 

template <class F, class Args, class = void> 
struct callable : std::false_type 
{ 
}; 

template <class F, class... Args> 
struct callable<F, callable_args<Args...>, std::result_of_t<F(Args...)>> : std::true_type 
{ 
}; 

} 

template <class F, class... Args> 
struct callable : impl::callable<F, impl::callable_args<Args...>> 
{ 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, Args...>::value; 


int main() 
{ 
    { 
     using Func = std::function<void()>; 
     auto result = callable_v<Func>; 
     std::cout << "test 1 (should be 1) = " << result << std::endl; 
    } 

    { 
     using Func = std::function<void(int)>; 
     auto result = callable_v<Func, int>; 
     std::cout << "test 2 (should be 1) = " << result << std::endl; 
    } 

    { 
     using Func = std::function<int(int)>; 
     auto result = callable_v<Func, int>; 
     std::cout << "test 3 (should be 1) = " << result << std::endl; 
    } 

    std::getchar(); 

    return EXIT_SUCCESS; 
} 

Używam kompilatora, który obsługuje C++ 14.

+0

Co z 'callable_t '? – NathanOliver

+0

@NathanOliver, wszelkie argumenty, które kompilator mógłby wydedukować są wywoływalne (aczkolwiek z ostrzeżeniem) powinny być prawidłowe, więc 'callable_t ' byłoby ok, jeśli 'foo' jest zdefiniowane jako say' foo (int, int) 'lub' foo (double, double) 'lub' foo (float, float) ', ale nie' foo (custom_type, custom_type) 'gdzie' custom_type' nie może zostać przekonwertowane. – keith

+0

@Marco A. Niestety nie :-( – keith

Odpowiedz

2

Skraca wykorzystanie std::result_of robić to, co chcesz, może wyglądać następująco:

template <class T, class, class... Args> 
struct callable: std::false_type { 
}; 

template <class T, class... Args> 
struct callable<T, decltype(std::result_of_t<T(Args...)>(), void()), Args...>:std::true_type { 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

trzeba pamiętać, że typ zwracany przez result_of jest zawsze typ wyniku funkcji mijamy do tej cechy według typu. Aby twoje sfinae działały, potrzebujesz metody, aby zmienić ten typ na nieważny w każdej możliwej sytuacji. Możesz to zrobić, używając sztuczki z decltype (decltype(std::result_of_t<T(Args...)>(), void())).

Edit:

Aby rozwinąć wątek od komentarzy na temat możliwych wad rozwiązania. Typ std::result_of_t<T(Args...)> nie musi być wyposażony w domyślny konstruktor nieparametryczny i jako taki, sfinae może powodować fałszywie ujemny wynik callable_v dla funkcji, która powoduje tego rodzaju typy. W komentarzach zaproponowałem obejście problemu, który tak naprawdę nie rozwiązuje problemu lub faktycznie generują nowe:

decltype(std::declval<std::result_of_t<T(Args...)>*>(), void()) 

Intencja tego kodu było uczynienie pracy sfinae jak w poprzednio proponowanego rozwiązania, ale w przypadku nie- typy konstrukcyjne, aby stworzyć łatwy do skonstruowania (myślę) obiekt wskaźnika do danego typu ... W tym rozumowaniu nie brałem pod uwagę typów, których nie można utworzyć wskaźnika do np. referencje.Ten jeden raz może być obejścia za pomocą jakiegoś dodatkowego klasy otoki:

decltype(std::declval<std::tuple<std::result_of_t<T(Args...)>>*>(), void()) 

lub rozkładających typ wyniku:

decltype(std::declval<std::decay_t<std::result_of_t<T(Args...)>>*>(), void()) 

ale myślę, że może nie być warto i może korzystać z void_t jest rzeczywiście bardziej proste rozwiązanie:

template <class...> 
struct voider { 
    using type = void; 
}; 

template <class... Args> 
using void_t = typename voider<Args...>::type; 

template <class T, class, class... Args> 
struct callable: std::false_type { 
}; 

template <class T, class... Args> 
struct callable<T, void_t<std::result_of_t<T(Args...)>>, Args...>:std::true_type { 
}; 

template <class F, class... Args> 
constexpr auto callable_v = callable<F, void, Args...>::value; 

[live demo]

+0

Podoba mi się to. Próbowałem zrobić coś podobnego, ale się nie udało. Świetny przykład (awansowałem) :-) W twoim przykładzie mógłbym użyć void_t zamiast triku typu decltype, ale przydałoby się nauczyć tej sztuczki decltype - dzięki! – keith

+0

@keith Wolę 'decltype' niż' void_t', ponieważ pozwala to na dostosowanie użytkownika ... Możesz na przykład zmienić dowolny typ na int, jeśli wolisz - nie jesteś skazany na zmianę wszystkiego, aby anulować ... Co więcej nie musisz implementować czegokolwiek, ponieważ dostajesz 'decltype' w C++ 11 po wyjęciu z pudełka –

+0

Czy są jakieś przypadki, w których to nie zadziała? Jest niezwykle prosty i elegancki. Istnieje podobna wersja tutaj: http: // talesofcpp.fusionfenix.com/post-11/true-story-call-me-maybe – keith

2

Oto jak bym podejść do tego:

namespace detail { 

template<typename Func, typename...Params> static auto helper(int) -> 
    decltype((void)std::declval<Func>()(std::declval<Params>()...), std::true_type{}); 

template<typename Func, typename...Params> static std::false_type helper(...); 

} 

template<typename Func, typename... Params> struct callable: 
    decltype(detail::helper<Func, Params...>(0)){}; 

template <class F, class... Args> constexpr auto callable_v = 
    callable<F, Args...>::value; 

demo

To wersja biedaka C++ 1z's is_callable, ale nie obsługuje wskaźników do członków. Poza tym, myślę, że to w porządku.

+0

dziękuję, dałem ci uprowadzenie, ponieważ jest bardzo interesujący i znacznie prostszy niż mój kod Nie pomaga mi zrozumieć, w jaki sposób mogę naprawić mój kod chociaż :-) – keith

2

Problem z oryginalnego kodu jest to, że używasz pakiet parametrów w kontekście braku wywnioskować

namespace impl 
{ 

    template <class F, class... Args> 
    struct callable : std::false_type 
    { 
    }; 

    template <class F, class... Args> 
    struct callable<F, std::result_of_t<F(Args...)>> : std::true_type 
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 
    { 
    }; 

} 

W tym momencie w kodzie źródłowym parsowania nie może być w żaden sposób, że std::result_of_t<Func, int> mogą przynieść kolejne wartości zwracanej, ale nie może być inna specjalizacja później w pliku jak w poniższym (bardzo wypaczone) snippet

namespace std { 

    template <> 
    struct result_of<Func(int)> { 
     using type = double; 
    }; 
} 

więc kompilator powinien sprawdzić wszystkie z nich w tym samym czasie, zanim będzie mógł odebrać właściwy.

To jest również powód, dla obejścia jak

template<class...> using void_t = void; 

namespace impl 
{ 

    template <typename...> 
    struct callable_args 
    { 
    }; 

    template <class F, class Args, class = void> 
    struct callable : std::false_type 
    { 
    }; 

    template <class F, class... Args> 
    struct callable<F, callable_args<Args...>, void_t<std::result_of_t<F(Args...)>>> : std::true_type 
    { 
    }; 

} 

pracy w Twoim przypadku: pomagają kompilator rozwiązać typ zależnego jako coś, co zawsze postanawia void. Pamiętaj, że powyższy kod jest obejściem obejścia i powinieneś raczej używać is_callable (C++ 17) lub studiować, jak jest wdrażana is_callable i uzyskać wgląd w jej techniczne wyzwania.

+0

Czekaj, czy na pewno nie wywnioskowany kontekst jest przyczyną tutaj i nie zawiedzie w dopasowywaniu wzorców do szablonów? Nie jestem przekonany, że pytanie można przeczytać, jak sugerujesz ... –

+0

Dzięki za informację, dałem ci uprowadzenie dzięki podziękowaniom. Będę szukał dla dostawcy kompilatora is_callable aby zrozumieć jak to działa. – keith