Here's an interesting variation of your idea. Pass a tuple of functions and a pack of arguments. Then call up all the functions from that tuple (from left to right), that can accept those given arguments. Furthermore, store all the return values (if any) from those functions called in a tuple. Here I'm using the void_t
(std::void_t
in C++17) aspect of SFINAE:
#include <iostream>
#include <tuple>
#include <type_traits>
template <typename T>
using void_t = void;
template <std::size_t N, typename VoidTFailed, typename EnableIfFailed, typename Tuple, typename... Args>
struct Try {
static bool execute (Tuple&&, Args&&...) {
std::cout << "No function found.\n";
return false;
}
};
template <std::size_t N, typename VoidTFailed, typename Tuple, typename... Args>
struct Try<N, VoidTFailed, std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1),
Try<N+1, void, void, Tuple, Args...>,
Try<N+1, int, int, Tuple, Args...> // If N == std::tuple_size<Tuple>::value - 1, then simply inherit from the primary template above, as inheriting from Try<N+1, void, void, Tuple, Args...> will cause a compiling error when std::tuple_element_t<N+1, Tuple> is instantiated in the specialization below.
> {};
struct NoReturnValue {
friend std::ostream& operator<< (std::ostream& os, const NoReturnValue&) {
return os << "[no value returned]";
}
};
template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args>
struct Continue {
static auto execute (Tuple&& functionTuple, Args&&... args) {
const auto singleTuple = std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...));
return std::tuple_cat (singleTuple, Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...));
}
};
template <std::size_t N, typename Tuple, typename... Args> // This specialization is only used if the function in question returns void.
struct Continue<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> {
static auto execute (Tuple&& functionTuple, Args&&... args) {
std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...);
return std::tuple_cat (std::make_tuple(NoReturnValue{}), Try<N+1, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...));
}
};
template <std::size_t N, typename Tuple, typename EnableIfFailed, typename... Args>
struct Stop {
static auto execute (Tuple&& functionTuple, Args&&... args) {
return std::make_tuple (std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...));
}
};
template <std::size_t N, typename Tuple, typename... Args> // This specialization is only used if the function in question returns void.
struct Stop<N, Tuple, std::enable_if_t<std::is_void<decltype(std::declval<std::tuple_element_t<N, std::decay_t<Tuple>>>()(std::declval<std::decay_t<Args>>()...))>::value>, Args...> {
static auto execute (Tuple&& functionTuple, Args&&... args) {
std::get<N>(std::forward<Tuple>(functionTuple))(std::forward<Args>(args)...);
return std::make_tuple(NoReturnValue{});
}
};
template <std::size_t N, typename Tuple, typename... Args>
struct Try<N, void_t<decltype(std::declval<std::tuple_element_t<N, Tuple>>()(std::declval<Args>()...))>,
std::enable_if_t<(N < std::tuple_size<Tuple>::value)>, Tuple, Args...> : std::conditional_t<(N < std::tuple_size<Tuple>::value - 1),
Continue<N, Tuple, void, Args...>,
Stop<N, Tuple, void, Args...>
> {};
template <typename Tuple, typename... Args>
auto executeAllPossible (Tuple&& functionTuple, Args&&... args) {
return Try<0, void, void, Tuple, Args...>::execute(std::forward<Tuple>(functionTuple), std::forward<Args>(args)...);
}
// Testing
int foo (int, char, bool, int, int) {std::cout << "foo\n"; return 8;}
bool bar (int, char, bool, double, long, bool) {std::cout << "bar\n"; return true;}
void goo (int, char&&, bool, float, int) {std::cout << "goo\n";}
double baz (int, const char, bool&&, double, std::size_t) {std::cout << "baz\n"; return 4.5;}
template <typename Ch, typename Tr, typename Tuple, std::size_t... Is>
void print_tuple_impl (std::basic_ostream<Ch, Tr>& os, const Tuple& t, std::index_sequence<Is...>) {
using A = int[];
(void)A{(void(os << (Is == 0 ? "" : ", ") << std::get<Is>(t)), 0)...};
}
template <typename Ch, typename Tr, typename... Args>
decltype(auto) operator<< (std::basic_ostream<Ch, Tr>& os, const std::tuple<Args...>& t) {
os << "tuple{";
print_tuple_impl(os, t, std::index_sequence_for<Args...>{});
return os << "}";
}
int main() {
const auto tuple = executeAllPossible (std::make_tuple(foo, bar, goo, baz), 2, 'c', true, 3.5, 8);
std::cout << std::boolalpha << "output = " << tuple << '\n';
}
Output:
foo goo baz
output = tuple{8, [no value returned], 4.5}