用多个输入向量中值的笛卡尔乘积调用lambda

时间:2018-09-07 17:41:43

标签: c++ templates lambda c++14 variadic-templates

我有多个intdouble s的向量:

std::vector<int>    iv = { 1, 2, 3, 4 };
std::vector<double> jv = { .5, 1., 1.5, 2. };
std::vector<int>    kv = { 5, 4, 3, 2 };

我需要处理每个向量的笛卡尔积:

for (int i : iv)
{
    for (double j : jv)
    {
        for (int k : kv)
        {
            process(i, j, k);
        }
    }
}

我想将其简化为一个呼叫

product(iv, jv, kv, [=](int i, double j, int k)
    {
        process(i, j, k);
    });
  • 输入向量的数量是可变的
  • 输入向量中存储的类型是可变的

这可能吗? (我正在使用C ++ 14)

4 个答案:

答案 0 :(得分:6)

这是一个简短的递归版本,仅适用于所有可迭代对象。为了简单起见,它使用const&来获取所有内容:

template <typename F>
void product(F f) {
    f();
}

template <typename F, typename C1, typename... Cs> 
void product(F f, C1 const& c1, Cs const&... cs) {
    for (auto const& e1 : c1) {
        product([&](auto const&... es){
            f(e1, es...);
        }, cs...);
    }   
}

将会是:

product(process, iv, jv, kv);

答案 1 :(得分:3)

您使用C ++ 14,因此可以使用std::index_sequence / std::make_index_sequence / std::index_sequence_for ...

我建议调用product(),首先传递函数,然后传递向量。

我的意思是

 product([=](int i, double j, int k) { process(i, j, k); }, iv, jv, kv);

这样,您可以将可变参数模板用于矢量。

我还建议了以下助手功能

template <typename F, std::size_t ... Is, typename Tp>
void productH (F f, std::index_sequence<Is...> const &, Tp const & tp)
 { f(std::get<Is>(tp)...); }

template <typename F, typename Is, typename Tp, typename T0, typename ... Ts>
void productH (F f, Is const & is, Tp const & tp, T0 const & t0, Ts ... ts)
 { 
   for ( auto const & val : t0 )
      productH(f, is, std::tuple_cat(tp, std::tie(val)), ts...);
 }

所以product()变得简单

template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
 { productH(f, std::index_sequence_for<Ts...>{}, std::make_tuple(), ts...); }

这个想法是提取并累积std::tuple中的值。给定最后一个std::tuple,调用函数使用std::tuplestd::get提取值,并使用std::index_sequence_for生成索引。

注意到没有必要向量是std::vector;可以是std::queuestd::array

以下是完整的编译示例

#include <array>
#include <tuple>
#include <deque>
#include <vector>
#include <utility>
#include <iostream>

template <typename F, std::size_t ... Is, typename Tp>
void productH (F f, std::index_sequence<Is...> const &, Tp const & tp)
 { f(std::get<Is>(tp)...); }

template <typename F, typename Is, typename Tp, typename T0, typename ... Ts>
void productH (F f, Is const & is, Tp const & tp, T0 const & t0, Ts ... ts)
 { 
   for ( auto const & val : t0 )
      productH(f, is, std::tuple_cat(tp, std::tie(val)), ts...);
 }

template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
 { productH(f, std::index_sequence_for<Ts...>{}, std::make_tuple(), ts...); }

void process (int i1, double d1, int i2)
 { std::cout << '[' << i1 << ',' << d1 << ',' << i2 << ']' << std::endl; }

int main ()
 {
   std::vector<int>       iv = { 1, 2, 3, 4 };
   std::array<double, 4u> jv = { { .5, 1., 1.5, 2. } };
   std::deque<int>        kv = { 5, 4, 3, 2 };

   product([=](int i, double j, int k) { process(i, j, k); }, iv, jv, kv);
 }

不幸的是,您不能使用C ++ 17来避免使用std::index_sequence / std::index_sequence_for / std::get()部分,而按如下方式使用std::apply()

template <typename F, typename Tp>
void productH (F f, Tp const & tp)
 { std::apply(f, tp); }

template <typename F, typename Tp, typename T0, typename ... Ts>
void productH (F f, Tp const & tp, T0 const & t0, Ts ... ts)
 { 
   for ( auto const & val : t0 )
      productH(f, std::tuple_cat(tp, std::tie(val)), ts...);
 }

template <typename F, typename ... Ts>
void product (F f, Ts ... ts)
 { productH(f, std::make_tuple(), ts...); }

答案 2 :(得分:2)

这是我的解决方案。这可能不是最佳选择,但是可以。

一个缺点是它仅适用于随机访问容器。

我将调用语法从product(a, b, c, lambda)更改为product(a, b, c)(lambda),因为该语法更易于实现。

#include <cstddef>
#include <iostream>
#include <vector>
#include <utility>

template <typename ...P, std::size_t ...I>
auto product_impl(std::index_sequence<I...>, const P &...lists)
{
    return [&lists...](auto &&func)
    {
        std::size_t sizes[]{lists.size()...};
        std::size_t indices[sizeof...(P)]{};
        std::size_t i = 0;

        while (i != sizeof...(P))
        {
            func(lists[indices[I]]...);

            for (i = 0; i < sizeof...(P); i++)
            {
                indices[i]++;
                if (indices[i] == sizes[i])
                    indices[i] = 0;
                else
                    break;
            }
        }
    };
}

template <typename ...P>
auto product(const P &...lists)
{
    return product_impl(std::make_index_sequence<sizeof...(P)>{}, lists...);
}

int main()
{
    std::vector<int> a = {1,2,3};
    std::vector<float> b = {0.1, 0.2};
    std::vector<int> c = {10, 20};

    product(a, b, c)([](int x, float y, int z)
    {
        std::cout << x << "  " << y << "  " << z << '\n';
    });

    /* Output:
    1  0.1  10
    2  0.1  10
    3  0.1  10
    1  0.2  10
    2  0.2  10
    3  0.2  10
    1  0.1  20
    2  0.1  20
    3  0.1  20
    1  0.2  20
    2  0.2  20
    3  0.2  20
    */
}

Try it live

答案 3 :(得分:0)

这是通过解释Barry的代码来实现的:

#include <iostream>
template <typename F>
void product(F f) {
    f();
}

template <typename F, typename C1, typename... Cs> 
void product(F f, C1 const& c1, Cs const&... cs) {
       product([&] ( auto const&... es){ 
            f(c1,es...);
          },
       cs...);
}

void process(int i, double j, int k)
{
  std::cout << i << " " << j << " " << k << std::endl;
}

int main()
{
   product(process, 1, 1.0, 2);
}

这只是调用process.的一种奇妙的方式。关键是f(c1,es...)创建了f的已管理函数,其中第一个参数被锁定在c1中。因此,当cs为空时,所有参数都会被处理,f()会调用process(1,1.0,2),请注意,此计算仅在编译时可用,而在运行时不可用。