如何元编程通用列表提取以构建函数调用

lrl*_*eon 6 c++ template-meta-programming c++11

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

double compute(list<T> pars)
Run Code Online (Sandbox Code Playgroud)

此方法使用通过接收的参数执行计算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
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

VtlQuantity compute(const VtlQuantity & x1, 
                    const VtlQuantity & x2,
                    // any number of pars according the class
                    const VtlQuantity & xn) const
Run Code Online (Sandbox Code Playgroud)

'VtlQuantity is a class representingdouble`,他们的单位和其他东西.

Mik*_*han 1

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

与由某些参数容器参数化的解决方案相比,这获得了多种灵活性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());
Run Code Online (Sandbox Code Playgroud)

你可以这样使用:

auto result = invoke(func,iter);
Run Code Online (Sandbox Code Playgroud)

应用于迭代器连续位置的 func参数。Niter

这样,您就不会进行范围检查,以N确保您的程序在这些位置可以合法访问参数。您将在实现中发现的范围检查代码将编译为空,如果您越界,将会出现 UB。

如果您想要范围检查,您可以改为编码:

auto result = invoke(func,iter,end);
Run Code Online (Sandbox Code Playgroud)

其中是与以通常方式界定可用范围末尾相同类型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);
}
Run Code Online (Sandbox Code Playgroud)

provided的两种特化function_traits<T>会将编译限制为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;
};
Run Code Online (Sandbox Code Playgroud)

自由函数的专门化function_traits<Ret(*)(ArgT, ArgRest...)>严格来说是一种多余的便利,因为它们也可以包装在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;
}
Run Code Online (Sandbox Code Playgroud)

的情况下:

    S s;
    std::cout << '=' << s.compute(li) << '\n';
Run Code Online (Sandbox Code Playgroud)

与您的特定问题特别相关,因为在这里我们调用 对 list 中传递的参数S::compute(std::list<int> const & li)应用另一个非静态方法。在实现中了解如何使用 lambda 方便地将调用对象绑定到我们可以传递给 的对象中。SliS::computeSS::computestd::functioninvoke

现场演示