This previous answer shows how to apply function based on the validity of a call: here. However, it applies to two functions. I was wondering if the concept could be generalized to N
functions using smart template programming tricks, in C++14.
The problem is the following:
template <std::size_t N, class... X>
/* [Return type] */ apply_on_validity(X&&... x)
{
// Tricks here
}
// The function would be equivalent to a hypothetical
// template <std::size_t N, class... F, class... Args>
// [Return type] apply_on_validity(F&&... f, Args&&... args)
// where N = sizeof...(F) is the number of functions
On the execution side:
apply_on_validity<3>(f1, f2, f3, a, b, c, d, e);
Would:
f1(a, b, c, d, e)
if the expression is valid, otherwisef2(a, b, c, d, e)
if the expression is valid, otherwisef3(a, b, c, d, e)
if the expression is valid, otherwiseAnd in all cases, it would return the result of the function that was executed.
On the calling side, the template parameter 3
indicates the number of functions that have been specified.
Is it doable in C++14, and if so, how?
In c++14:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, N>, std::index_sequence<Js...>, Args&& args)
{
// Nothing here
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
auto apply_on_validity_impl(int, std::integral_constant<std::size_t, I>, std::index_sequence<Js...>, Args&& args)
-> decltype(std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...))
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(char, std::integral_constant<std::size_t, I>, std::index_sequence<Js...> seq, Args&& args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, I + 1>{}, seq, std::forward<Args>(args));
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N>(0, std::integral_constant<std::size_t, 0>{}, std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
DEMO
In c++17:
#include <tuple>
#include <utility>
#include <type_traits>
#include <cstddef>
template <std::size_t N, std::size_t I, std::size_t... Js, typename Args>
decltype(auto) apply_on_validity_impl(std::index_sequence<Js...> seq, Args&& args)
{
if constexpr (I == N)
{
}
else if constexpr (std::is_invocable_v<std::tuple_element_t<I, Args>, std::tuple_element_t<Js + N, Args>...>)
{
return std::get<I>(std::forward<Args>(args))(std::get<Js + N>(std::forward<Args>(args))...);
}
else
{
return apply_on_validity_impl<N, I + 1>(seq, std::forward<Args>(args));
}
}
template <std::size_t N, typename... Args>
decltype(auto) apply_on_validity(Args&&... args)
{
return apply_on_validity_impl<N, 0>(std::make_index_sequence<sizeof...(Args) - N>{}, std::forward_as_tuple(std::forward<Args>(args)...));
}
DEMO 2
Here's another fun one for kicks, abusing overload resolution. We're going to transform each function into another function that takes a rank
argument, combine all of our functions together, and then just invoke them. The selection will just fall out naturally from the overload set.
Our ranker is just this ladder of types:
template <int I> struct rank : rank<I-1> { };
template <> struct rank<0> { };
We need a function transformer:
template <class T, class F>
auto prepend_arg(F&& f) {
return [f=std::forward<F>(f)](T, auto&&... args)
-> decltype(std::forward<F>(f)(std::forward<decltype(args)>(args)...))
{
return std::forward<F>(f)(std::forward<decltype(args)>(args)...);
};
}
And then:
template <std::size_t N, std::size_t... Is, std::size_t... Js,
class... X>
decltype(auto) apply_impl(std::index_sequence<Is...>,
std::index_sequence<Js...>,
X&&... x)
{
auto all = std::forward_as_tuple(std::forward<X>(x)...);
return overload(
// all of our function cases
prepend_arg<rank<N-Is>>(std::get<Is>(all))...,
// base case: do nothing
[](rank<0>, auto&&...) {}
)(rank<N>{}, std::get<N+Js>(all)...);
// ^^^^^^^^^^^^^^^^^^^^^^^^
// pass in all the arguments
}
template <std::size_t N, class... X>
decltype(auto) apply_on_validity(X&&... x) {
return apply_impl<N>(
std::make_index_sequence<N>{},
std::make_index_sequence<sizeof...(X)-N>{},
std::forward<X>(x)...);
}
I leave overload()
as an exercise.
Use boost::hana::overload_linearly
:
hana::overload_linearly(f1, f2, f3)(a, b, c, d, e)
If none of the expressions are valid, it's a compile error, but it's easy to make it do nothing in that case:
hana::overload_linearly(f1, f2, f3, [](auto&&...) {})(a, b, c, d, e)
Alternatively, use boost::hof::first_of
, which does the same thing
I had answered this in the previous iteration, but the nary version had a bug, and clang had an internal compiler error which made it hard to find the bug.
I have since fixed the bug. So here is a pile of metaprogramming, followed by solving your problem.
First, a homebrew version of C++2a's 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;
As it happens, std::result_of_t is the trait we want to test.
template<class Sig>
using can_call = can_apply< std::result_of_t, Sig >;
now can_call< Some(Sig,Goes,Here) > is true_type iff the expression you want can be called.
Now we write some compile-time if dispatch machinery.
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... ) returns a lambda. The first of the SomeBools which is compile-time truthy (has a ::value that evaluates to true in a boolean context) determines what the returned lambda does. Call that the dispatch index.
It returns the dispatch_index'd argument to the next call, and an empty lambda if that is one-past-the-end of the list.
Now we want to be able to dispatch on a set of possibilities. To make this simple (heh), we'll use 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>{} );
}
which lets us expand parameter packs without having to build new functions.
Then we can write auto_dispatch
as a single function template:
template<class...Fs>
auto auto_dispatch( Fs&&... fs ) {
auto indexer = index_over<sizeof...(fs)>();
// some compilers dislike lambdas with unexpanded parameter packs.
// this helps with that:
auto helper = [&](auto I)->decltype(auto){
return std::get<decltype(I)::value>( std::forward_as_tuple( decltype(fs)(fs)... ) );
};
// Get 0 through N-1 as compile-time constants:
return indexer
(
[helper](auto...Is){
// make tuple of functions:
auto fs_tuple = std::forward_as_tuple( helper(Is)... );
// This is what is returned from the `auto_dispatch` function
// it perfect forwards into the correct lambda passed to `auto_dispatch`
// based on which is the first one which can be invoked by
// args...
return [fs_tuple](auto&&...args) {
// dispatcher knows which one can be called
auto dispatcher = dispatch(can_call<Fs(decltype(args)...)>{}...);
// here we get the first one that can be called, or an empty lambda:
auto&& f0 = dispatcher(std::get<decltype(Is)::value>(fs_tuple)...);
// here we do the actual call:
std::forward<decltype(f0)>(f0)(decltype(args)(args)...);
};
}
);
}
with test code:
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
The only N-ary recursive template instantiation above is dispatch_index
. I can make that log-depth with a bit of work (divide and conquer). Getting it constant depth is hard. I will think on it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With