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

时间:2016-08-26 12:35:27

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

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

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, ..)的实现。

我想找到一种方法来驱使&#34;这个重复的过程;具体而言,提取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

&#39; VtlQuantity is a class representing double`&#39; s,他们的单位和其他内容。

4 个答案:

答案 0 :(得分:3)

您可以执行以下操作:

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

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

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)。

答案 1 :(得分:1)

下面的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<N>(list)指定我们希望在编译时从列表中弹出多少元素。

从列表中获取元组后,我们可以使用std::experimental::apply通过将元组的元素应用为参数来调用函数:这由call_with_list<N>(list, func)完成。

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

  1. 创建std::tuple<T, T, T, T, ...>,其中T重复N次。

  2. 致电list<T>::pop_back() N次,将这些项目放入元组中。

  3. 要解决第一个问题,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())...};。这里使用了上述相同的逗号运算符“技巧”。

      

    我可以用C ++ 11编写吗?

    是的,但会稍微“讨厌”。

答案 2 :(得分:1)

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编写)。

它需要F类型和T类型,并告诉您T可以传递给F以使通话有效的最少数量。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) ); } 的数量,则会破坏您的模板实例化堆栈,并且您的编译器会抱怨或死亡。

auto

转换为C ++ 11唯一令人烦恼的是compute上的arity返回类型。我必须重写我的std::function

这个版本应该自动检测甚至非函数指针的arity,让你用lambdas或<script type="text/javascript"> var thestring = "<img src=\"/images/item/aegis-of-the-legion.gif\" alt=\"LoL Item: Aegis of the Legion\"><br>"; var thestring2 = "<img src=\"/images/otherstuff/aegis-of-the-legion.gif\" alt=\"LoL Item: Aegis of the Legion\"><br>"; function ParseIt(incomingstring) { var pattern = /"\/images\/item\/(.*)" /; if (pattern.test(incomingstring)) { return pattern.exec(incomingstring)[1]; } else { return ""; } //return pattern.test(incomingstring) ? pattern.exec(incomingstring)[1] : ""; } </script> 或者你有什么来调用它。

答案 3 :(得分:1)

这是一个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<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;
};

免费功能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;
}

案例:

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

与您的特定问题特别相关,因为我们在这里打电话 S::compute(std::list<int> const & li)应用另一种非静态方法 S的{​​{1}}对列表li中提供的参数。请参阅实施中 S::compute如何使用lambda可以方便地绑定两者 我们可以将S对象和S::compute调用到std::function 传递给invoke

Live demo