Thanks to everyone who provided an answer, and special thanks to @HolyBlackCat for pointing out the restriction on the standard library—I wasn't aware of it.
After some thought, I've come up with a solution that meets my needs and would like to share it with you. Please feel free to critique it if I’ve overlooked any potential issues or if it breaks any C++ standard restrictions.
The code below supports basic types like std::pair
, std::tuple
, and std::array
, as well as my custom specializations, to the best of my understanding.
#include <iostream>
#include <algorithm>
#include <string>
#include <array>
#include <tuple>
#include <functional>
// Define a simple struct MyStruct with two different types
struct MyStruct { int i; double d; };
struct MyOtherStruct { int i; double d; std::string s; };
// Specialize std::tuple_size for MyStruct to enable tuple-like behavior
namespace std {
// Standard allows specialization of std::tuple_size for user-defined types
template <> struct tuple_size<MyStruct> : std::integral_constant<std::size_t, 2> { }; // MyStruct has 2 members
template <> struct tuple_size<MyOtherStruct> : std::integral_constant<std::size_t, 3> { }; // MyOtherStruct has 3 members
}
namespace My {
// Support for all std::tuple-like types using std::apply
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(const StdStruct& a) {
return std::get<N>(a);
}
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(StdStruct& a) {
return std::get<N>(a);
}
template <std::size_t N, typename StdStruct>
constexpr decltype(auto) Get(StdStruct&& a) {
return std::get<N>(a);
}
// Specialization of Get for MyStruct to access its members
template <std::size_t N>
constexpr decltype(auto) Get(const MyStruct& a) {
if constexpr (N == 0)
return (a.i);
else if constexpr (N == 1)
return (a.d);
}
// Specialization of Get for MyOtherStruct to access its members
template <std::size_t N>
constexpr decltype(auto) Get(const MyOtherStruct& a) {
if constexpr (N == 0)
return (a.i);
else if constexpr (N == 1)
return (a.d);
else if constexpr (N == 2)
return (a.s);
}
// Convert a struct to a tuple using index sequence as someone else suggested
template <typename Tuple, std::size_t... I>
constexpr auto ToTupleImpl(Tuple&& t, std::index_sequence<I...>) {
return std::make_tuple(Get<I>(t)...);
}
// Public interface to convert a struct to a tuple
template <typename Tuple>
constexpr auto ToTuple(const Tuple& s) {
return ToTupleImpl(s, std::make_index_sequence<std::tuple_size<Tuple>::value>());
}
// Implementation of Apply to invoke a callable with tuple elements
template <class Callable, class Struct, size_t... Indices>
constexpr decltype(auto) Apply_impl(Callable&& Obj, Struct&& Strct, std::index_sequence<Indices...>) noexcept(
noexcept(std::invoke(std::forward<Callable>(Obj), Get<Indices>(std::forward<Struct>(Strct))...))) {
return std::invoke(std::forward<Callable>(Obj), Get<Indices>(std::forward<Struct>(Strct))...);
}
// Public interface to apply a callable to a tuple-like structure
template <class Callable, class Struct>
constexpr decltype(auto) Apply(Callable&& Obj, Struct&& Strct) noexcept(
noexcept(Apply_impl(std::forward<Callable>(Obj), std::forward<Struct>(Strct), std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Struct>>>{}))) {
return Apply_impl(std::forward<Callable>(Obj), std::forward<Struct>(Strct),
std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Struct>>>{});
}
}
int main() {
// Example usage of MyStruct
constexpr MyStruct ms{42, 3.14};
const MyOtherStruct mos {42, 3.14, "My other struct"};
// Apply a lambda to MyStruct converted to a tuple
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, My::ToTuple(ms));
// Apply a lambda to a std::pair
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, std::pair {2, 3});
// Apply a lambda to a std::array
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, std::array {4, 5});
// Apply a lambda directly to MyStruct
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, ms);
// Apply a lambda directly to MyOtherStruct
My::Apply([](auto&&... args) {((std::cout << args << ' '), ...); std::cout << "\n";}, mos);
return 0;
}