2017-02-03 5 views
16

Проблема заключается в следующем, в C++14:Выберите функцию, чтобы применять на основе справедливости выражения

  • Давайте две функции FV&& valid_f, FI&& invalid_f и аргументы Args&&... args
  • Функция apply_on_validity должна применяться valid_f на args, если выражение std::forward<FV>(valid_f)(std::forward<Args>(args)...) действительно
  • В противном случае и если std::forward<FV>(invalid_f)(std::forward<Args>(args)...) является действительным выражением, apply_on_validity должен применяться invalid_f по args
  • Иначе apply_on_validity не должны делать ничего

Я предполагаю, что код должен выглядеть как-то вроде этого:

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 
} 

Но я не знаю, как сделать это. Есть идеи?


Ссылка на обобщение here.

+1

Вы можете рассмотреть глядя на [ 'void_t'] (HTTP: // en.cppreference.com/w/cpp/types/void_t) и имеющий SFINAE, основанный на выражении 'void_t'. – vsoftco

+0

, но вы хотите это, когда 'valid_f' и' invalid_f' являются аргументами 'apply_on_validity()'? Не с двумя известными и фиксированными функциями? – max66

+0

Не могу * подождать * для 'if constexpr' ... – Barry

ответ

16

Take:

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

, а затем:

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

небольшое объяснение: 'decltype' использует SFINAE, чтобы гарантировать, что функция шаблона включена для указанного выражение. Параметр 'rank' предназначен для устранения неоднозначности и приоритезации перегрузок в случае, если допустима более чем одна перегрузка функции шаблона. – bolov

+0

Ну, я только что закончил свое решение и обнаружил, что вы отправили именно то, что я придумал. – Justin

11

Piotr Skotnicki's answer превосходна, но код, как это заставляет меня чувствовать себя вынужденными указать, сколько чистого C++ 17 будут получены благодаря constexpr if и дополнительным типам, например is_callable: Demo Demo* Эта версия создает больше предупреждений, но проще

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

Какие у вас «причины правильности»? – Barry

+0

_Вы можете заменить эти сложные 'is_callable_v' чеки с чем-то вроде' is_callable_v ', для большинства сценариев. Когда это не так? Я считаю, что они в точности эквивалентны в этом контексте (т. Е. При использовании по типам, выводимым из ссылок пересылки). –

+1

@Barry и yuri kilochek: Я изначально думал о ref-квалифицированных функторах, когда писал это, но кажется, что 'is_callable' его охватывает. Теперь gcc любит давать мне много предупреждений, но работает – AndyG

7

Вот альтернативный ответ, только для пинков. Нам нужно 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); } 

И is_callable. Так как вы только функции поддержки, мы можем сделать это как:

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

И тогда мы можем построить логику на месте:

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

@ Этот метод обобщается здесь: http://stackoverflow.com/questions/42031216/apply-the-first-valid-function-of-a-set-of -n-функции? – Vincent

3

Во-первых, доморощенный вариант C++ 2a-х 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; 

Как это происходит, std :: result_of_t - это признак, который мы хотим проверить.

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

< Теперь can_call Some (Sig, идет, здесь)> является true_type тогда и только тогда выражение, которое вы хотите можно назвать.

Теперь мы пишем какое-то время для компиляции в случае отправки.

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

Отправка (SomeBools ...) возвращает лямбда. Первый из SomeBools, который является правдой времени компиляции (имеет значение ::, которое вычисляет значение true в булевом контексте) определяет, что делает возвращенная лямбда. Позвоните, что индекс отправки.

Он возвращает аргумент dispatch_index'd для следующего вызова и пустую лямбда, если это один конец конца списка.

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

и сделано, live example.

Мы могли бы сделать это общее для включения новой версии. Первый 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>{}); 
} 

Тогда 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)...); 
     }; 
    } 
); 
} 

с тестовым кодом:

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

 Смежные вопросы

  • Нет связанных вопросов^_^