Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove last item from function parameter pack

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.

like image 530
Diederick C. Niehorster Avatar asked Oct 15 '25 06:10

Diederick C. Niehorster


1 Answers

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...);
like image 134
Barry Avatar answered Oct 17 '25 20:10

Barry



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!