2017-02-03 53 views
16

Problem jest następujący, w C++14:Wybierz funkcję do zastosowania, na podstawie ważności wyrazem

  • Rzućmy dwie funkcje FV&& valid_f, FI&& invalid_f i argumenty Args&&... args
  • powinno stosować valid_f na Funkcja apply_on_validityargs, jeśli wyrażenie std::forward<FV>(valid_f)(std::forward<Args>(args)...) jest ważne
  • W przeciwnym razie i jeśli std::forward<FV>(invalid_f)(std::forward<Args>(args)...) jest prawidłowym wyrażeniem, apply_on_validity powinno zastosować invalid_f na args
  • Inaczej apply_on_validity powinien robić nic

Chyba kod powinien wyglądać mniej więcej tak:

template <class FV, class FI, class... Args, /* Some template metaprog here */> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    // Apply valid_f by default 
    std::forward<FV>(valid_f)(std::forward<Args>(args)...); 
} 

template <class FV, class FI, class... Args, /* Some template metaprog here */> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    // Apply invalid_f if valid_f does not work 
    std::forward<FV>(invalid_f)(std::forward<Args>(args)...); 
} 

template <class FV, class FI, class... Args, /* Some template metaprog here */> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    // Do nothing when neither valid_f nor invalid_f work 
} 

Ale ja naprawdę nie wiem jak to zrobić. Dowolny pomysł?


Link do uogólnienia here.

+1

można rozważyć patrząc na [ 'void_t'] (http: // en.cppreference.com/w/cpp/types/void_t) i mając SFINAE w oparciu o wyrażenie 'void_t'. – vsoftco

+0

ale czy chcesz to, gdy 'valid_f' i' invalid_f' są argumentami 'apply_on_validity()'? Nie z dwoma znanymi i naprawionymi funkcjami? – max66

+0

Nie mogę * czekać * na 'jeśli constexpr' ... – Barry

Odpowiedz

16

Weź udział:

template <int N> struct rank : rank<N-1> {}; 
template <> struct rank<0> {}; 

, a następnie:

template <class FV, class FI, class... Args> 
auto apply_on_validity_impl(rank<2>, FV&& valid_f, FI&& invalid_f, Args&&... args) 
    -> decltype(std::forward<FV>(valid_f)(std::forward<Args>(args)...), void()) 
{ 
    std::forward<FV>(valid_f)(std::forward<Args>(args)...); 
} 

template <class FV, class FI, class... Args> 
auto apply_on_validity_impl(rank<1>, FV&& valid_f, FI&& invalid_f, Args&&... args) 
    -> decltype(std::forward<FI>(invalid_f)(std::forward<Args>(args)...), void()) 
{ 
    std::forward<FI>(invalid_f)(std::forward<Args>(args)...); 
} 

template <class FV, class FI, class... Args> 
void apply_on_validity_impl(rank<0>, FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 

} 

template <class FV, class FI, class... Args> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    return apply_on_validity_impl(rank<2>{}, std::forward<FV>(valid_f), std::forward<FI>(invalid_f), std::forward<Args>(args)...); 
} 

DEMO

+1

małe wyjaśnienie: 'decltype' używa SFINAE, aby upewnić się, że funkcja szablonu jest włączona dla określonego wyrażenie. Parametr 'rank' służy do rozróżnienia i priorytetyzacji przeciążeń w przypadku, gdy więcej niż jedno przeciążenie funkcji szablonu jest prawidłowe. – bolov

+0

Po prostu skończyłem moje rozwiązanie i odkryłem, że napisałeś dokładnie to, co wymyśliłem. – Justin

11

Piotr Skotnicki's answer jest super, ale jak kod sprawia, że ​​czuję się w obowiązku zwrócić uwagę, jak wiele czystsze C++ 17 będzie dzięki constexpr if i dodatkowe cechy typu, takie jak is_callable: Demo Demo* Ta wersja tworzy kolejne ostrzeżenia, ale jest prostsza

template <class FV, class FI, class... Args> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    if constexpr (std::is_callable_v<FV(Args...)>) 
     std::cout << "Apply valid_f by default\n"; 
    else 
    { 
     if constexpr (std::is_callable_v<FI(Args...)>) 
      std::cout << "Apply invalid_f if valid_f does not work\n"; 
     else 
      std::cout << "Do nothing when neither valid_f nor invalid_f work\n"; 
    } 
} 
+0

Jakie są "przyczyny poprawności"? – Barry

+0

_Możesz zastąpić te skomplikowane sprawdzenia 'is_callable_v' z czymś takim, jak' is_callable_v ', dla większości scenariuszy. Kiedy tak nie jest? Sądzę, że są one dokładnie równoważne w tym kontekście (tj. Gdy są używane w typach wywodzących się z odniesień do przekazywania). –

+1

@Barry i yuri kilochek: Początkowo myślałem o refartowanych funktorach, kiedy to pisałem, ale wygląda na to, że 'is_callable' ma to w sobie. Teraz gcc lubi dawać mi wiele ostrzeżeń, ale działa – AndyG

7

Oto alternatywna odpowiedź, po prostu dla zabawy. Potrzebujemy static_if:

template <class T, class F> T&& static_if(std::true_type, T&& t, F&&) { return std::forward<T>(t); } 
template <class T, class F> F&& static_if(std::false_type, T&& , F&& f) { return std::forward<F>(f); } 

i is_callable. Skoro tylko wspiera funkcje, możemy to zrobić jak:

template <class Sig, class = void> 
struct is_callable : std::false_type { }; 

template <class F, class... Args> 
struct is_callable<F(Args...), void_t<decltype(std::declval<F>()(std::declval<Args>()...))>> 
: std::true_type 
{ }; 

A potem możemy skonstruować logikę w lokalu:

template <class FV, class FI, class... Args> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    auto noop = [](auto&&...) {}; 

    static_if(
     is_callable<FV&&(Args&&...)>{}, 
     std::forward<FV>(valid_f), 
     static_if(
      std::is_callable<FI&&(Args&&...)>{}, 
      std::forward<FI>(invalid_f), 
      noop 
     ) 
    )(std::forward<Args>(args)...); 
} 
+0

@ Ta metoda jest generalizowana tutaj: http://stackoverflow.com/questions/42031216/apply-the-first-valid-function-of-a-setof -n-funkcje? – Vincent

3

Najpierw homebrew wersja C++ 2a na is_detected:

#include <utility> 
#include <type_traits> 
#include <iostream> 
#include <tuple> 

namespace details { 
    template<class...>using void_t=void; 
    template<template<class...>class Z, class=void, class...Ts> 
    struct can_apply:std::false_type{}; 
    template<template<class...>class Z, class...Ts> 
    struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:std::true_type{}; 
} 
template<template<class...>class Z, class...Ts> 
using can_apply = typename details::can_apply<Z, void, Ts...>::type; 

Tak się składa, że ​​std :: result_of_t jest cechą, którą chcemy przetestować.

template<class Sig> 
using can_call = can_apply< std::result_of_t, Sig >; 

teraz can_call < Niektórzy (Sig, Goes, tutaj)> jest true_type IFF ekspresji chcesz można nazwać.

Teraz piszemy trochę czasu na kompilację w przypadku maszyn wysyłkowych.

template<std::size_t I> 
using index_t=std::integral_constant<std::size_t, I>; 
template<std::size_t I> 
constexpr index_t<I> index_v{}; 

constexpr inline index_t<0> dispatch_index() { return {}; } 
template<class B0, class...Bs, 
    std::enable_if_t<B0::value, int> =0 
> 
constexpr index_t<0> dispatch_index(B0, Bs...) { return {}; } 
template<class B0, class...Bs, 
    std::enable_if_t<!B0::value, int> =0 
> 
constexpr auto dispatch_index(B0, Bs...) { 
    return index_v< 1 + dispatch_index(Bs{}...) >; 
} 

template<class...Bs> 
auto dispatch(Bs...) { 
    using I = decltype(dispatch_index(Bs{}...)); 
    return [](auto&&...args){ 
    return std::get<I::value>(std::make_tuple(decltype(args)(args)..., [](auto&&...){})); 
    }; 
} 

wysyłka (SomeBools ...) zwraca wartość lambda. Pierwszy z SomeBools, który jest prawdą podczas kompilacji (ma wartość :: która sprawdza się w kontekście boolowskim), określa, co robi zwracana lambda. Zadzwoń do indeksu wysyłki.

Zwraca argument dispatch_index'd do następnego połączenia i pustą wartość lambda, jeśli jest to jeden-koniec-koniec listy.

template <class FV, class FI, class... Args /*, Some template metaprog here */> 
void apply_on_validity(FV&& valid_f, FI&& invalid_f, Args&&... args) 
{ 
    dispatch(
    can_call<FV(Args...)>{}, 
    can_call<FI(Args...)>{} 
)(
    [&](auto&& valid_f, auto&&)->decltype(auto) { 
     return decltype(valid_f)(valid_f)(std::forward<Args>(args)...); 
    }, 
    [&](auto&&, auto&& invalid_f)->decltype(auto) { 
     return decltype(invalid_f)(valid_f)(std::forward<Args>(args)...); 
    } 
)(
    valid_f, invalid_f 
); 
} 

i gotowe, live example.

Moglibyśmy zrobić ten rodzajowy, aby umożliwić niedrogą wersję. Pierwszy index_over:

template<class=void, std::size_t...Is > 
auto index_over(std::index_sequence<Is...>){ 
    return [](auto&&f)->decltype(auto){ 
    return decltype(f)(f)(std::integral_constant<std::size_t, Is>{}...); 
    }; 
} 
template<std::size_t N> 
auto index_over(std::integral_constant<std::size_t, N> ={}){ 
    return index_over(std::make_index_sequence<N>{}); 
} 

Następnie auto_dispatch:

template<class...Fs> 
auto auto_dispatch(Fs&&... fs) { 
    auto indexer = index_over<sizeof...(fs)>(); 
    auto helper = [&](auto I)->decltype(auto){ 
    return std::get<decltype(I)::value>(std::forward_as_tuple(decltype(fs)(fs)...)); 
    }; 
    return indexer 
    (
    [helper](auto...Is){ 
     auto fs_tuple = std::forward_as_tuple(helper(Is)...); 
     return [fs_tuple](auto&&...args) { 
     auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...); 
     auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...); 
     std::forward<decltype(f0)>(f0)(decltype(args)(args)...); 
     }; 
    } 
); 
} 

z kodem testu:

auto a = [](int x){ std::cout << x << "\n"; }; 
auto b = [](std::string y){ std::cout << y << "\n"; }; 
struct Foo {}; 
auto c = [](Foo){ std::cout << "Foo\n"; }; 
int main() { 
    auto_dispatch(a, c)(7); 
    auto_dispatch(a, c)(Foo{}); 
    auto_dispatch(a, b, c)(Foo{}); 
    auto_dispatch(a, b, c)("hello world"); 
} 

Live example