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.
make_first_valid_callable(f1, f2, f3)(a, b, c, d, e);
, which does not force users to manually keep track of function count and is more composable. – Cleanse