Skip to content

Instantly share code, notes, and snippets.

@Hamondorf
Last active October 19, 2020 08:33
Show Gist options
  • Save Hamondorf/5e227ae6c204321842a4b0138630e76c to your computer and use it in GitHub Desktop.
Save Hamondorf/5e227ae6c204321842a4b0138630e76c to your computer and use it in GitHub Desktop.
Tuplify, like std::tie but cooler (I hope). Structured bindings to specific members using their names or their getters.
#ifndef HAM_TUPLIFY_HPP
#define HAM_TUPLIFY_HPP
#include <tuple>
#include <type_traits>
#include <utility>
// There's examples and stuff towards the bottom
namespace ham
{
// Implementation things and such
namespace impl
{
template <class T>
struct remove_rval_ref
: std::conditional<std::is_rvalue_reference_v<T>, std::remove_reference_t<T>, T> {};
template <class T>
using remove_rval_ref_t = typename remove_rval_ref<T>::type;
template <class T>
inline constexpr bool is_reference_wrapper_v = false;
template <class U>
inline constexpr bool is_reference_wrapper_v<std::reference_wrapper<U>> = true;
template <auto fn, class T>
inline constexpr std::invoke_result_t<decltype(fn), T> tuplify_invoke(T&& object)
noexcept(std::is_nothrow_invocable_v<decltype(fn), T>)
{
using Fn = std::decay_t<decltype(fn)>;
using Decayed = std::decay_t<T>;
using R = std::conditional_t<is_reference_wrapper_v<Decayed>,
std::unwrap_ref_decay_t<T>, T>;
if constexpr (std::is_member_object_pointer_v<Fn>)
if constexpr (std::is_pointer_v<Decayed>)
return (*std::forward<R>(object)).*fn;
else
return std::forward<R>(object).*fn;
else if constexpr (std::is_member_function_pointer_v<Fn>)
if constexpr (std::is_pointer_v<Decayed>)
return ((*std::forward<R>(object)).*fn)();
else
return (std::forward<R>(object).*fn)();
else
return fn(std::forward<T>(object));
}
template <class T, decltype(auto)... callables>
using tuplify_result_t = std::tuple<
remove_rval_ref_t<std::invoke_result_t<decltype(callables), T>>...>;
} // namespace impl
template <class T>
struct get_referenced_class
: std::enable_if<std::is_object_v<T>,
std::decay_t<std::unwrap_ref_decay_t<T>>>
{ };
template <class T>
using get_referenced_class_t = typename get_referenced_class<T>::type;
template <class T>
using get_ref_class_t = get_referenced_class_t<T>;
// Helper class used to store a set of callables to be used when
// tuplifying a class
template <class T, decltype(auto)... callables>
struct tuplifier
{
static_assert((std::is_invocable_v<decltype(callables), T> && ...),
"All callables must be invocable with T as their sole argument or be "
"data member pointers to T (or the referenced type in the case T is "
"a std::reference_wrapper).");
constexpr tuplifier() = default;
constexpr tuplifier(tuplifier const&) = default;
constexpr tuplifier(tuplifier &&) = default;
// Needs a little improvement but basically if the functions are invocable
// and the classes are the same everythings good
template <class U, class = std::enable_if_t<
std::is_same_v<
std::decay_t<std::unwrap_ref_decay_t<T>>,
std::decay_t<std::unwrap_ref_decay_t<U>>
>
&& (std::is_invocable_v<decltype(callables), T> && ...)
>>
constexpr tuplifier(tuplifier<U, callables...> const&) noexcept {}
template <class U, class = std::enable_if_t<
std::is_same_v<
std::decay_t<std::unwrap_ref_decay_t<T>>,
std::decay_t<std::unwrap_ref_decay_t<U>>
>
&& (std::is_invocable_v<decltype(callables), T> && ...)
>>
constexpr tuplifier(tuplifier<U, callables...>&&) noexcept {}
};
template <class T, decltype(auto)... callables>
using tuplifier_t = tuplifier<std::type_identity_t<T>, callables...>;
// Creates a tuple of references or values (if the invoke result is an rvalue)
// by invoking a set of given callables on a given object
template <decltype(auto)... callables, class T>
[[nodiscard]]
inline constexpr auto tuplify(T&& object, tuplifier_t<T, callables...> = {})
noexcept(
(std::is_nothrow_invocable_v<decltype(callables), T> && ...))
-> impl::tuplify_result_t<T, callables...>
{
return { impl::tuplify_invoke<callables>(std::forward<T>(object))... };
}
// Creates a tuples of forwarding references by invoking a set of callables
// on a given object
template <decltype(auto)... callables, class T>
[[nodiscard]]
inline constexpr auto forward_tuplify(T&& object, tuplifier_t<T, callables...> = {})
noexcept(
(std::is_nothrow_invocable_v<decltype(callables), T> && ...))
-> std::tuple<std::invoke_result_t<decltype(callables), T>...>
{
return { impl::tuplify_invoke<callables>(std::forward<T>(object))... };
}
// Example stuff below (feel free to uncomment and test things)
/*
struct S
{
int x, y;
constexpr int fn() const { return x + y; }
};
constexpr S s{ 1, 2 };
constexpr std::tuple<int, int, int> tup = tuplify<&S::y, &S::x, &S::fn>(S{ 1, 2 });
constexpr std::tuple<int const&, int const&, int> tup2 = tuplify<&S::y, &S::x, &S::fn>(s);
inline std::tuple<int&&, int&&> tup3 = forward_tuplify<&S::x, &S::y>(S{ 1, 2 });
struct A { int x, y; };
struct B { int y, x; };
struct C { int j, k; };
struct D { int w, x, y, z; };
template <class T>
using tuplifier_xy = tuplifier<T, &get_ref_class_t<T>::x, &get_ref_class_t<T>::y>;
constexpr void fn()
{
A a{ 1, 2 }; // a.x = 1, a.y = 2
B b{ 3, 4 }; // b.x = 4, b.y = 3
C c{ 5, 6 }; // c.j = 5, c.k = 6
D d{ 7, 8, 9, 10 }; // d.w = 7, d.x = 8. d.y = 9, d.z = 10
{
// all good
auto& [a_x, a_y] = a; // a_x == a.x, a_y == a.y
// debugging's gonna be fun
auto& [b_x, b_y] = b; // b_x == b.y, b_y == b.x
// probably not what you wanted
auto& [c_x, c_y] = c; // c_x == c.j, c_y == c.k
// if you just wanted x and y, you now have to deal with w and z too
auto& [d_w, d_x, d_y, d_z] = d;
}
{
// still good
auto& [a_x, a_y] = tuplify<&A::x, &A::y>(a);
// order doesn't matter now
auto& [b_x, b_y] = tuplify(b, tuplifier_xy<B>{}); // tuplifier helper
// compilation error since there are no x and y members of C
// auto& [c_x, c_y] = tuplify(c, tuplifier_xy<C>{});
// only get the members you want
auto& [d_x, d_y] = tuplify(d, tuplifier_xy<D>{});
}
}
//*/
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment