tutorial resueltos kuby johnson estadística estadistica español elemental ejercicios c++ templates c++14 overloading template-meta-programming

c++ - resueltos - Elija la función para aplicar en función de la validez de una expresión



tutorial qgis 2.18 español pdf (4)

Aquí hay una respuesta alternativa, sólo para patadas. Necesitamos un 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); }

Y una is_callable . Ya que solo estás apoyando funciones, podemos hacerlo como:

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 { };

Y luego podemos construir la lógica en su lugar:

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)...); }

El problema es el siguiente, en C++14 :

  • Tengamos dos funciones FV&& valid_f , FI&& invalid_f , y argumentos Args&&... args
  • La función apply_on_validity debe aplicarse valid_f en args si la expresión std::forward<FV>(valid_f)(std::forward<Args>(args)...) es válida
  • De lo contrario, y si std::forward<FV>(invalid_f)(std::forward<Args>(args)...) es una expresión válida, apply_on_validity debe aplicar invalid_f en args
  • De apply_on_validity contrario, apply_on_validity no debería hacer nada.

Supongo que el código debería parecerse a algo como esto:

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 }

Pero realmente no sé cómo hacer eso. ¿Alguna idea?

Enlace a la generalización here .


Primero, se is_detected una versión is_detected de C ++ 2a:

#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;

Como sucede, std :: result_of_t es el rasgo que queremos probar.

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

ahora can_call <Some (Sig, Goes, Here)> es true_type si se puede llamar a la expresión que desee.

Ahora escribimos algo de compilación en tiempo de despacho.

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&&...){}) ); }; }

dispatch (SomeBools ...) devuelve un lambda. El primero de SomeBools, que es de verdad de tiempo de compilación (tiene un :: value que se evalúa como verdadero en un contexto booleano) determina lo que hace el lambda devuelto. Llama a eso el índice de despacho.

Devuelve el argumento dispatch_index''d a la siguiente llamada, y un lambda vacío si es el último de la lista.

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 ); }

y hecho, ejemplo vivo .

Podríamos hacer este genérico para habilitar esta versión. Primer 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>{} ); }

Entonces 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)...); }; } ); }

con código de prueba:

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" ); }

Ejemplo vivo


Tomar:

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

y entonces:

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


La respuesta de Piotr Skotnicki es excelente, pero un código como ese me obliga a señalar cuánto más limpio será C ++ 17 gracias a constexpr if y rasgos de tipo adicionales como is_callable : Demo Demo * Esta versión crea más advertencias pero es más simple

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"; } }