The desire is to have only one wrapper for all typename T
which support structured bindings for example via tuple_size
and tuple_element
without runtime overhead(contexpr
s, SFINAE). There's a function encode
with accepts T obj
as an argument and calls encode_impl
with more specific arguments and type arguments.
The article https://playfulprogramming.blogspot.com/2016/12/serializing-structs-with-c17-structured.html uses a bunch of arity functions to achieve the same result. But as far as I understand tuples provide std::tuple_size
which is possible to utilize.
#include <tuple>
#include <utility>
class Aa {
public:
Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a1;
else if constexpr (N == 1) return a2;
}
private:
int a1;
int a2;
};
class Bb {
public:
Bb(Aa a_, int b_): a(a_), b(b_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a;
else if constexpr (N == 1) return b;
}
private:
Aa a;
int b;
};
namespace std {
// Aa
template<>
struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Aa> {
using type = decltype(std::declval<Aa>().get<N>());
};
// Bb
template<>
struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Bb> {
using type = decltype(std::declval<Bb>().get<N>());
};
}
template <size_t N>
using size = std::integral_constant<size_t, N>;
template<typename T>
void encode(T t) {
encode_impl<?std::tuple_size<T>?()>(T t, ?std::tuple_size<T>);
}
template<?>
encode_impl(T t, ?) {
std::cout << "It works";
}
The expectations of resolving the issue are the understanding of notation which must be used to compile the code snippet. Right now you can see ?
in many places.
If it's not possible to do using std::tuple_size
then alternative solutions are welcome. Arity function from the article for classes don't work but that's a bit different question.
Are you asking how to apply the return values of some_type::get<i>()
as the arguments of a function?
Off the top of my head you would write encode
and encode_impl
something like this:
template<typename T>
void encode(T const& t, std::ostream& os)
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ ((os << t.template get<I>() << (I != last ? ", " : " ")), 0)... };
os << "}" << std::endl;
}
This disgusting looking pack expansion in encode_impl
is using the comma operator just to force evaluation of its left hand operand and discard the result, then evaluate the literal 0
and store it in the dummy array temp
. The pack expansion is used to initialize an array so that the arguments are evaluated in the correct order (left to right).
UPDATE:
Okay so I think what you want is to make your own type trait test is_tuple_like
that returns true if the type T
is "tuple-like" which just requires that the expression std::tuple_size<T>::value
is well formed and the expression declval<T&>().template get<std::size_t(0)>()
is well formed. From there you can write a function that prints the elements of "tuple-like" types and if any of those elements are tuple-like, print their elements, recursively. Here's what I came up with:
template
<
typename T,
typename tp_enabled =
std::void_t
<
decltype(std::tuple_size<T>::value),
decltype(std::declval<T&>().template get<std::size_t(0)>())
>
>
constexpr auto
is_tuple_like(int const)noexcept->bool
{
return true;
}
template
<typename T, typename tp_arg>
constexpr auto
is_tuple_like(tp_arg const)noexcept->bool
{
return false;
}
template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << std::endl;
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>(0)>
{
os << t << (is_last ? " " : ", ");
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>(0)>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << (is_last ? " " : ", ");
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ (encode_one<I == last>(t.template get<I>(), os), 0)... };
os << "}";
}
int main () {
auto a = Aa(1, 1);
encode(a, std::cout);
auto b = Bb(a, 1);
encode(b, std::cout);
return 0;
}
OUTPUT:
{ 1, 1 }
{ { 1, 1 }, 1 }
UPDATE 2: So it turns out the above implementation of is_tuple_like
compiles fine on GCC and the latest version of Clang (8.0.0) but fails to compile on Clang 7.0.0 so here is a version that works on Clang 7.0.0 it uses a variable template instead of a function template:
#include <tuple>
#include <utility>
#include <iostream>
class Aa {
public:
Aa(int a1_, int a2_): a1(a1_), a2(a2_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a1;
else if constexpr (N == 1) return a2;
}
private:
int a1;
int a2;
};
class Bb {
public:
Bb(Aa a_, int b_): a(a_), b(b_) {}
template<std::size_t N>
decltype(auto) get() const {
if constexpr (N == 0) return a;
else if constexpr (N == 1) return b;
}
private:
Aa a;
int b;
};
namespace std {
// Aa
template<>
struct tuple_size<Aa> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Aa> {
using type = decltype(std::declval<Aa>().get<N>());
};
// Bb
template<>
struct tuple_size<Bb> : std::integral_constant<std::size_t, 2> {};
template<std::size_t N>
struct tuple_element<N, Bb> {
using type = decltype(std::declval<Bb>().get<N>());
};
}
template
<typename T, typename tp_enabled = std::void_t<>>
constexpr bool is_tuple_like = false;
template
<typename T>
constexpr bool
is_tuple_like
<
T,
std::void_t
<
decltype(std::tuple_size<T>::value),
decltype(std::declval<T&>().template get<std::size_t(0)>())
>
> = true;
template<typename T>
auto encode(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << std::endl;
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<!is_tuple_like<T>>
{
os << t << (is_last ? " " : ", ");
}
template
<bool is_last, typename T>
auto
encode_one(T const& t, std::ostream& os)->
std::enable_if_t<is_tuple_like<T>>
{
encode_impl(t, os, std::make_index_sequence<std::tuple_size<T>::value>{ });
os << (is_last ? " " : ", ");
}
template
<typename T, std::size_t... I>
void encode_impl(T const& t, std::ostream& os, std::index_sequence<I...> const)
{
constexpr auto last = sizeof...(I) - 1;
os << "{ ";
[[maybe_unused]] int const temp[] =
{ (encode_one<I == last>(t.template get<I>(), os), 0)... };
os << "}";
}
int main () {
auto a = Aa(1, 1);
encode(a, std::cout);
auto b = Bb(a, 1);
encode(b, std::cout);
return 0;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With