4

I am writing a method to extract values from arbitrarily nested structs. I am almost there, but would like to also provide an option to convert the value retrieved (by default no conversion). Since parameter packs can't be followed by another template parameter, I have to fudge this a bit. The below works except for the indicated line:

#include <iostream> #include <type_traits> typedef struct { int a; int b; } bar; typedef struct { int c; bar d; } baz; template <typename T, typename S, typename... Ss> auto inline getField(const T& obj, S field1, Ss... fields) { if constexpr (!sizeof...(fields)) return obj.*field1; else return getField(obj.*field1, fields...); } template <typename Obj, typename Out, class ...C, typename... T> auto inline getFieldC(const Obj& obj, Out, T C::*... field) { return static_cast<Out>(getField(obj, field...)); } template<class T> struct tag_t { using type = T; }; template<class...Ts> using last = typename std::tuple_element_t< sizeof...(Ts) - 1, std::tuple<tag_t<Ts>...> >::type; template <typename Obj, typename... T> auto getMyFieldWrapper(const Obj& obj, T... field) { if constexpr (std::is_member_object_pointer_v<last<Obj, T...>>) return getField(obj, field...); else return getFieldC(obj, last<Obj, T...>{}, field...); // <- this doesn't compile, need a way to pass all but last element of field } int main() { baz myObj; std::cout << getMyFieldWrapper(myObj, &baz::c); // works std::cout << getMyFieldWrapper(myObj, &baz::d, &bar::b); // works std::cout << getMyFieldWrapper(myObj, &baz::d, &bar::b, 0.); // doesn't work } 

How do I implement the indicated line? I'm using the latest MSVC, and am happy to make full use of C++17 to keep things short and simple.

1
  • I rolled your edit back - don't edit answers into your questions. Commented Aug 24, 2018 at 3:35

2 Answers 2

3

Usually more helpful to invert the flow. First, write a higher-order function that forwards an index sequence:

template <typename F, size_t... Is> auto indices_impl(F f, std::index_sequence<Is...>) { return f(std::integral_constant<size_t, Is>()...); } template <size_t N, typename F> auto indices(F f) { return indices_impl(f, std::make_index_sequence<N>()); } 

That is just generally useful in lots of places.

In this case, we use it to write a higher-order function to drop the last element in a pack:

template <typename F, typename... Ts> auto drop_last(F f, Ts... ts) { return indices<sizeof...(Ts)-1>([&](auto... Is){ auto tuple = std::make_tuple(ts...); return f(std::get<Is>(tuple)...); }); } 

And then you can use that:

return drop_last([&](auto... elems){ return getMyField(obj, last<Obj, T...>{}, elems...); }, field...); 

References omitted for brevity.


Of course, if you want to combine both and just rotate, you can do:

// Given f and some args t0, t1, ..., tn, calls f(tn, t0, t1, ..., tn-1) template <typename F, typename... Ts> auto rotate_right(F f, Ts... ts) { auto tuple = std::make_tuple(ts...); return indices<sizeof...(Ts)-1>([&](auto... Is){ return f( std::get<sizeof...(Ts)-1>(tuple), std::get<Is>(tuple)...); }); } 

used as:

return rotate_right([&](auto... elems){ return getMyField(obj, elems...); }, field...); 
Sign up to request clarification or add additional context in comments.

4 Comments

Very cool technique! Thanks! Sadly, both don't compile on me: coliru.stacked-crooked.com/a/99cf9113ccf0570b
That was a typo in my code, fixed now. works! thanks!
The drop_last and rotate_right functions call std::get<Is>, but Is is a parameter pack containing std::integral_constant<size_t>, right? How does that work? I thought the first template argument to std::get needed to be size_t, not std::integral_constant?
Ah! I guess it's because std::integral_constant<size_t> has a constexpr size_t operator()(), so is implicitly convertible to size_t even in constant expressions?
0

How do I implement the indicated line?

Not sure to understand what do you want but... it seems to me that you can make it calling an intermediate function

template <std::size_t ... Is, typename ... Ts> auto noLastArg (std::index_sequence<Is...> const &, std::tuple<Ts...> const & tpl) { return getMyField(std::get<Is>(tpl)...); } 

you can rewrite your function as follows

template <typename Obj, typename ... T> auto getMyFieldWrapper (Obj const & obj, T ... field) { if constexpr (std::is_member_object_pointer<last<Obj, T...>>::value ) return getMyField(obj, field...); else return noLastArg(std::make_index_sequence<sizeof...(T)>{}, std::make_tuple(obj, field...)); } 

The idea is pack the arguments for getMyField in a std::tuple of sizeof...(T)+1u elements (+1 because there is also obj) and call getMyField() unpacking the first sizeof...(T) of them.

But isn't clear, to me, if you want also last<Obj, T...>{}.

In this case, the call to noLastArg() become

 return noLastArg(std::make_index_sequence<sizeof...(T)+1u>{}, std::make_tuple(obj, last<Obj, T...>{}, field...)); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.