【问题标题】:How to metaprogram a generic list extraction for building a function call如何将通用列表提取构建函数调用
【发布时间】:2017-01-03 03:20:09
【问题描述】:

我有一系列具有以下签名的方法的类:

double compute(list<T> pars)

此方法使用通过pars 接收的参数执行计算。对于每个compute(list) 方法,我都有另一个compute(x1, x2, ..., xn),这是实现实际计算的方法。因此,compute(pars) 应该这样做:

double compute(list<T> pars)
{
  T x1 = list.pop_back();
  T x2 = list.pop_back();
  // .. so on until last parameter xn
  T xn = list.pop_back();

  return compute(x1, x2, .., xn); // here the real implementation is called
}

这种模式重复了很多次,唯一可以改变的是pars列表的大小,当然还有compute(x1, x1, ..)的实现。

我想找到一种方法来“干燥”这个重复的过程;具体来说,提取pars 列表中的参数并构建对compute(x1, x2, .., xn) 的调用。我一直在尝试做一些宏观技巧,但没有成功。

我的问题是它是否存在某种基于元编程的方式,允许我实现一次 compute(list&lt;T&gt; pars) 并简单地重用它,以便执行对 compute(x1, x2, ..., xn) 的调用

EDIT:这是另一个compute(x1, ...)的签名

VtlQuantity compute(const VtlQuantity & x1, 
                    const VtlQuantity & x2,
                    // any number of pars according the class
                    const VtlQuantity & xn) const

'VtlQuantityis a class representingdouble`'s,他们的单位和其他东西。

【问题讨论】:

  • compute() 不知道pars 的大小,这几乎排除了元编程。 P.S.:除非你喜欢无缘无故地一直复制lists,否则compute() 应该引用它的参数。
  • 在编译时是否知道 pars 的大小?
  • @VittorioRomeo:不是的。大小在运行时间中是已知的。但是,它可以从将接收compute(x1, x2, .., xn)的参数数量推导出来。
  • 如何推断?传递给compute(x1, ...) 的参数数量基于pars 的大小,反之亦然。
  • @SamVarshavchik:是的,我可以通过引用接收列表并通过迭代器获取参数

标签: c++ c++11 template-meta-programming


【解决方案1】:

您可以执行以下操作:

template <typename Func, typename T, std::size_t ... Is>
decltype(auto) apply(Func&& f, const std::list<T>& pars, std::index_sequence<Is...>)
{
    std::vector<T> v(pars.rbegin(), pars.rend());

    return std::forward<Func>(f)(v.at(Is)...);
}

template <std::size_t N, typename Func, typename T>
decltype(auto) apply(Func&& f, const std::list<T>& pars)
{
    return apply(std::forward<Func>(f), pars, std::make_index_sequence<N>());
}

用法类似于:

apply<6>(print, l);

Demo

要自动计算函数的数量,您可以创建一个特征:

template <typename F> struct arity;

template <typename Ret, typename ...Args> struct arity<Ret(Args...)>
{
    static constexpr std::size_t value = sizeof...(Args);
};

然后

template <typename Func, typename T>
decltype(auto) apply(Func&& f, const std::list<T>& pars)
{
    constexpr std::size_t N = arity<std::remove_pointer_t<std::decay_t<Func>>>::value;
    return apply(std::forward<Func>(f), pars, std::make_index_sequence<N>());
}

Demo

您必须丰富 arity 以支持 Functor(作为 lambda)。

【讨论】:

  • 感谢您的关注。有没有办法向编译器解释参数的数量?有些看起来像一个函数名称并推断它接收多少个参数。我认为那将是唯一缺少的东西。
  • @lrleon 删除N,将Func 替换为RArgs... 并使用R f(Args...) 并使用sizeof...(Args) 而不是N
  • 使用数组来避免额外的动态分配会更好,但这是比较棘手的,所以我猜不是。您还可以预先保留向量然后插入元素,这可能会更高效。
  • 假设计算是一个C++ 函数,这很有效。问题是compute(x1, x2, ..., xn) 是一种方法。我会尝试找出如何做到这一点
  • 完成!我使用来自 [github.com/kennytm/utils/blob/master/traits.hpp]arity 来获得成员的数量。然后我修改了你提出的递归元编程模式,对于一种方法来说,它要简单得多。再次感谢您和其他人
【解决方案2】:

这是一个 C++11 解决方案,适用于更一般的问题类型 一个函数或仿函数F,采用N 类型T 参数并返回类型Ret,到N 参数 在某些输入迭代器的连续位置。

与由一些参数的T 容器参数化的解决方案相比,这获得了一些灵活性:-

  • 您可以从序列中任意N 大小的范围中提取参数。

  • 序列不必是T 的容器 - 尽管它必须是可转换为T 的东西的序列。

  • 您可以从后到先(如您所做的那样)或先到后提取参数, 来自标准容器类型或任何支持正向和反向迭代器的容器类型。

  • 您甚至可以将F 应用于直接从某些输入流中使用的参数,无需 中间提取。

  • 当然,您可以改变主意 无需更改功能应用解决方案即可提供参数。

界面

template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop());

你可以这样使用:

auto result = invoke(func,iter);

func 应用于迭代器连续位置N 的参数 iter.

这样,您就不会检查N 参数是否可以合法访问 在这些职位上你的程序。您将发现的范围检查代码 在实现中将编译为空,如果您越界 会有UB。

如果你想检查范围,你可以用代码代替:

auto result = invoke(func,iter,end);

其中end 是与iter 相同类型的迭代器,用于分隔结束 可用范围以通常的方式。在这种情况下,std::out_of_range 将 如果N 超出范围的大小,则抛出。

实施

#include <type_traits>
#include <functional>
#include <string>

template<typename T>
struct function_traits;

template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<Ret(*)(ArgT, ArgRest...)>
{
    static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
    using first_arg_type = ArgT;
    using return_type = Ret;
};

template <typename Ret, typename ArgT, typename... ArgRest>
struct function_traits<std::function<Ret(ArgT, ArgRest...)>>
{
    static constexpr std::size_t n_args = 1 + sizeof...(ArgRest);
    using first_arg_type = ArgT;
    using return_type = Ret;
};

namespace detail {

template<typename Left, typename Right>
typename std::enable_if<!std::is_same<Left,Right>::value>::type
range_check(Left, Right, std::string const &){}

template<typename Left, typename Right>
typename std::enable_if<std::is_same<Left,Right>::value>::type
range_check(Left start, Right end, std::string const & gripe) {
    if (start == end) {
        throw std::out_of_range(gripe);
    }
}

template<
    std::size_t N, typename Func, typename InIter, typename Stop, 
    typename ...Ts
>
typename std::enable_if<
    N == function_traits<typename std::decay<Func>::type>::n_args, 
    typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter, Stop, Ts...args)
{
    return f(args...);
}

template<
    std::size_t N, typename Func, typename InIter, typename Stop,
    typename ...Ts
>
typename std::enable_if<
    N != function_traits<typename std::decay<Func>::type>::n_args,
    typename function_traits<typename std::decay<Func>::type>::return_type
>::type
invoke(Func && f, InIter it, Stop stop, Ts...args)
{
    range_check(it,stop,
        "Function takes more arguments than are available "
        "in `" + std::string(__PRETTY_FUNCTION__) + '`');
    using arg_type = typename 
        function_traits<typename std::decay<Func>::type>::first_arg_type;
    auto arg = static_cast<arg_type>(*it);
    return invoke<N + 1>(std::forward<Func>(f),++it,stop,args...,arg);
}

} // namespace detail

template<typename Func, typename InIter, typename Stop = std::nullptr_t>
typename function_traits<typename std::decay<Func>::type>::return_type
invoke(Func && f, InIter it, Stop stop = Stop())
{
    return detail::invoke<0>(std::forward<Func>(f),it,stop);
}

function_traits&lt;T&gt; 提供的两个特化将限制 编译为函数类型T 至少需要一个参数,这应该 足以满足可能的应用。如果您需要支持 对采用 0 个参数的类型进行调用,然后您可以通过以下方式对其进行扩充:

template <typename Ret>
struct function_traits<Ret(*)()>
{
    static constexpr std::size_t n_args = 0;
    using return_type = Ret;
};

template <typename Ret>
struct function_traits<std::function<Ret()>>
{
    static constexpr std::size_t n_args = 0;
    using return_type = Ret;
};

免费功能的专业化function_traits&lt;Ret(*)(ArgT, ArgRest...)&gt;, 严格来说是一种多余的便利,因为它们也可以包装在std::function 对象,因为您必须为任何比免费功能更高级的东西做事。

演示

对于练习所讨论的功能的程序,您可以附加:

#include <iostream>
#include <list>
#include <vector>
#include <deque>
#include <sstream>
#include <iterator>

struct num
{
    double d;
    explicit operator double() const {
        return d;
    }
};

double add4(double d0, double d1, double d2, double d3)
{
    std::cout << d0 << '+' << d1 << '+' << d2 << '+' << d3 << "\n="; 
    return d0 + d1 + d2 + d3;
}

int multiply2(int i0, int i1)
{
    std::cout << i0 << '*' << i1 << "\n="; 
    return i0 * i1;
}

struct S
{
    int subtract3(int i0, int i1, int i2) const
    {
        std::cout << i0 << '-' << i1 << '-' << i2 << "\n="; 
        return i0 - i1 - i2;
    }
    int compute(std::list<int> const & li) const {
        std::function<int(int,int,int)> bind = [this](int i0, int i1, int i2) {
            return this->subtract3(i0,i1,i2);
        };
        return invoke(bind,li.begin());
    }
};


int main()
{
    std::vector<double> vd{1.0,2.0,3.0,4.0};
    std::vector<double> vdshort{9.0};
    std::list<int> li{5,6,7,8};
    std::deque<num> dn{num{10.0},num{20.0},num{30.0},num{40.0}};
    std::istringstream iss{std::string{"10 9 8"}};
    std::istream_iterator<int> it(iss); 
    std::cout << invoke(add4,vd.rbegin()) << '\n';
    std::cout << invoke(multiply2,li.begin()) << '\n';
    std::cout << invoke(add4,dn.rbegin()) << '\n';   
    std::cout << invoke(multiply2,++it) << '\n';
    S s;
    std::cout << '=' << s.compute(li) << '\n';
    try {
        std::cout << invoke(add4,vdshort.begin(),vdshort.end()) << '\n';
    } catch(std::out_of_range const & gripe) {
        std::cout << "Oops :(\n" << gripe.what() << '\n';
    }

    return 0;
}

案例:

    S s;
    std::cout << '=' << s.compute(li) << '\n';

与您的特定问题特别相关,因为我们在这里调用 S::compute(std::list&lt;int&gt; const &amp; li) 应用另一个非静态方法 S 到列表 li 中传递的参数。见实施 S::compute 的使用 lambda 如何方便地绑定 将S 对象和S::compute 调用为std::function 我们可以 传递给invoke

Live demo

【讨论】:

    【解决方案3】:

    C++17 解决方案如下。 wandbox link

    (感谢Jarod42 大大简化了)

    • 假设参数N 的数量在编译时是已知的,但列表可以有任意大小。

    • 如示例所示多次调用pop_back(),然后调用一个函数。


    template <typename T>
    struct list
    {
        T pop_back() { return T{}; }
    };
    
    namespace impl
    {    
        template<typename TList, std::size_t... TIs>
        auto list_to_tuple(TList& l, std::index_sequence<TIs...>)
        {
            using my_tuple = decltype(std::make_tuple((TIs, l.pop_back())...));
            return my_tuple{((void)TIs, l.pop_back())...};
        }
    }
    
    template<std::size_t TN, typename TList>
    auto list_to_tuple(TList& l)
    {
        return impl::list_to_tuple(l, std::make_index_sequence<TN>());
    }
    
    template <std::size_t TN, typename TList, typename TF>
    auto call_with_list(TList& l, TF&& f)
    {
        return std::experimental::apply(f, list_to_tuple<TN>(l));
    }
    
    void test_compute(int, int, int)
    {
        // ...
    }
    
    int main()
    {
        list<int> l{};
        call_with_list<3>(l, test_compute);
    }
    

    它是如何工作的?

    我们的想法是我们将一个列表“转换”为一个元组,在编译时使用list_to_tuple&lt;N&gt;(list) 指定我们想要从列表中弹出多少元素。

    从列表中获取元组后,我们可以使用std::experimental::apply 调用函数,将元组的元素作为参数应用:这是由call_with_list&lt;N&gt;(list, func) 完成的。

    要从列表中创建一个元组,需要做两件事:

    1. 创建一个std::tuple&lt;T, T, T, T, ...&gt;,其中T 重复N 次。

    2. 调用list&lt;T&gt;::pop_back()N 次,将项目放入元组中。

    为了解决第一个问题,decltype 用于获取以下可变参数扩展的类型:std::make_tuple((TIs, l.pop_back())...)。使用逗号运算符,以便 TIs, l.pop_back() 计算结果为 decltype(l.pop_back())

    为了解决第二个问题,在std::initializer_list 元组构造函数中使用了可变参数展开,它保证了求值顺序:return my_tuple{((void)TIs, l.pop_back())...};。此处使用了与上述相同的逗号运算符“trick”。


    我可以用 C++11 写吗?

    是的,但会更“烦人”。

    【讨论】:

    • using my_tuple = decltype(std::make_tuple((Is, l.pop_back())...));(未指定顺序,但不改变类型)和my_tuple res{(Is, l.pop_back())...};initializer_list强制顺序评估)似乎更简单。
    • @Jarod42:好点,我会改进答案 - 谢谢。任何简单的评估顺序都可以在make_tuple 中强制使用以避免always_t 并且只有return std::make_tuple(((void)TIs, l.pop_back())...);
    • 没有函数调用为make_tuple,但正如我所说,我们可以使用它来知道类型,然后使用initializer_list
    【解决方案4】:
    template<class T> using void_t = void;
    
    template<class T, class F, std::size_t N=0, class=void>
    struct arity:arity<T, F, N+1> {};
    
    template<class F, class T, class Indexes>
    struct nary_result_of{};
    
    template<std::size_t, class T>
    using ith_T=T;
    
    template<class F, class T, std::size_t...Is>
    struct nary_result_of<F, T, std::index_sequence<Is...>>:
      std::result_of<F( ith_T<Is, T> )>
    {};
    
    template<class T, class F, std::size_t N>
    struct arity<T, F, N, void_t<
      typename nary_result_of<F, T, std::make_index_sequence<N>>::type
    >>:
      std::integral_constant<std::size_t, N>
    {};
    

    arity 使用了一个 C++14 特性(索引序列,易于用 C++11 编写)。

    它接受FT 类型,并告诉您可以传递给FTs 的最少数量以使调用有效。如果没有数量的T 符合条件,它会破坏您的模板实例化堆栈,并且您的编译器会抱怨或死亡。

    template<class T>
    using strip = typename std::remove_reference<typename std::remove_cv<T>::type>::type;
    
    namespace details {
      template<class T, std::size_t N, class F, class R,
        std::size_t...Is
      >
      auto compute( std::index_sequence<Is...>, F&& f, R&& r ) {
        std::array<T, N> buff={{
          (void(Is), r.pop_back())...
        }};
        return std::forward<F>(f)( buff[Is]... );
      }
    }
    template<class F, class R,
      class T=strip< decltype( *std::declval<R&>().begin() ) >
    >
    auto compute( F&& f, R&& r ) {
      return details::compute( std::make_index_sequence<arity<F,T>{}>{}, std::forward<F>(f), std::forward<R>(r) );
    }
    

    转换为 C++11 唯一真正令人讨厌的是 auto 上的返回类型 compute。我必须重写我的arity

    这个版本应该自动检测非函数指针的数量,让你用 lambdas 或 std::functions 或你有什么来调用它。

    【讨论】:

      猜你喜欢
      • 2016-10-22
      • 2021-10-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多