Last active
October 19, 2020 08:33
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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