从功能参数包中删除最后一项

时间:2018-08-23 20:37:41

标签: c++ templates variadic-templates c++17 template-meta-programming

我正在编写一种从任意嵌套的结构中提取值的方法。我快到了,但还想提供一个选项来转换检索到的值(默认情况下不进行转换)。由于参数包后面不能跟另一个模板参数,因此我不得不对此稍加捏造。除指示的行外,下面的工作:

#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
}

如何实现指示的行?我正在使用最新的MSVC,并且很高兴充分利用C ++ 17来使事情简短明了。

2 个答案:

答案 0 :(得分:3)

通常更有助于反转流程。首先,编写一个转发索引序列的高阶函数:

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>());
}

通常在很多地方都有用。

在这种情况下,我们使用它编写一个高阶函数来删除包中的最后一个元素:

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)...);
    });
}

然后您可以使用它:

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

为简洁起见,省略了引用。


当然,如果您想同时结合并旋转,则可以执行以下操作:

// 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)...);
    });
}   

用作:

return rotate_right([&](auto... elems){
    return getMyField(obj, elems...);
}, field...);

答案 1 :(得分:0)

  

如何实现指示的行?

不确定要了解什么,但是...在我看来,您可以让它调用中间函数

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)...); }

您可以按以下方式重写函数

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...));
 }

这个想法是将getMyField的参数打包在std::tuple个元素的sizeof...(T)+1u中(+1也是因为obj其中第一个getMyField()

对我来说还不清楚,如果你也想sizeof...(T)

在这种情况下,对last<Obj, T...>{}的呼叫变为

noLastArg()