【问题标题】:Given a tuple with arbitrary number of vectors holding different types how do I extract the vector with minimum size?给定一个具有任意数量的具有不同类型的向量的元组,我如何提取具有最小大小的向量?
【发布时间】:2018-12-17 13:15:01
【问题描述】:

在我的应用程序中,我生成了一个包含许多保存值的向量的元组。我希望有一种通用的方法来遍历元组以提取具有最小值的单个向量。

例如:

auto t = std::make_tuple(std::vector<int>({1,2}), std::vector<double>({1.0, 2.0, 3.0}));

如何提取包含最小值的向量?

【问题讨论】:

  • 先提取,再提取,比较大小?
  • 我澄清了这个问题 - 当我得到元组时,我无法对特定条目进行硬编码,因为我不知道元组包含多少个向量。
  • 每个向量是否拥有不同的元素类型,或者你可以有多个,例如vector&lt;int&gt;s?
  • 您愿意使用std::variant&lt;std::vector&lt;int&gt;, std::vector&lt;double&gt;&gt; 作为您的示例吗?
  • @Caleth 是的,但我想我必须接受boost::variant

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


【解决方案1】:

给定一个包含不同类型的向量的元组,我如何提取最小尺寸的向量?

你不能你不能直接。

因为它们是不同的类型,决定是基于值(而不是类型),所以你无法决定提取的编译时间(std::tuple不能是constexpr),而C++是强类型的语言。

您可以做的最佳最简单的事情是提取具有最小尺寸的向量的索引。因为在这种情况下,提取的值是一个整数(例如std::size_t),您可以遍历元组中的向量以选择具有较少元素的向量。

不同的是,如果您必须以最小大小提取std::array

auto t = std::make_tuple(std::array<int, 2u>({1,2}),
                         std::array<double, 3u>({1.0, 2.0, 3.0}));

因为大小(第一个为2u,第二个为3u)是已知的编译时间,因此您可以选择第二个数组编译时间。

如果你可以使用C++17,你可以使用std::variant,这样一个类型可以包含你所有类型的元组。

正如 Caleth 所指出的(谢谢),如果您只能将 C++11/C++14 与 boost 库一起使用,则可以使用 boost::variant。但我不知道,所以我不能给你看一个具体的例子(但你可以看到 Caleth 的答案)

下面是一个有两种类型元组的例子

template <typename T1, typename T2>
std::variant<T1, T2> getLess (std::tuple<T1, T2> const & tp)
 {
   std::variant<T1, T2>  v;

   if ( std::get<0>(tp).size() < std::get<1>(tp).size() )
       v = std::get<0>(tp);
   else
       v = std::get<1>(tp);

   return v;
 }

int main ()
 {
   std::vector<int>     vi {1, 2, 3};
   std::vector<double>  vd {1.0, 2.0};

   auto gl = getLess(std::make_tuple(vi, vd));
 }

这适用于两种类型的元组。但是使用 N 类型元组(N 高)变得复杂,因为你不能写一些东西

auto min_length = std::get<0>(tp).size();
auto min_index  = 0u;

for ( auto ui = 1u ; ui < N ; ++ui )
   if ( std::get<ui>(tp).size() < min_length )
    {
       min_length = std::get<ui>(tp).size();
       min_index  = ui;
    }

因为您不能将运行时值作为ui 传递给std::get&lt;&gt;()

分配变体时同样的问题。你不能简单地写

v = std::get<min_index>(tp);

因为min_index 是一个运行时值。

你必须通过switch()

switch ( min_length )
 {
   case 0: v = std::get<0>(tp); break;
   case 1: v = std::get<1>(tp); break;
   case 2: v = std::get<2>(tp); break;
   case 3: v = std::get<3>(tp); break;
   case 4: v = std::get<4>(tp); break;

   // ...

   case N-1: v = std::get<N-1>(tp); break;
 };

或类似的东西。

如您所见,如果您希望 getLess() 函数是可变参数,它会变得越来越复杂。

对于可变参数的情况,我能想象的最好的(但是是 C++17 解决方案;请参阅 Caleth 对 C++11 解决方案的回答)是使用辅助函数和模板折叠,如下所示。

#include <tuple>
#include <vector>
#include <variant>
#include <iostream>

template <typename ... Ts, std::size_t ... Is>
auto getLessHelper (std::tuple<Ts...> const & tp,
                    std::index_sequence<0, Is...> const &)
 {
   std::variant<Ts...> var_ret  { std::get<0>(tp) };
   std::size_t         min_size { std::get<0>(tp).size() };

   ((std::get<Is>(tp).size() < min_size ? (var_ret = std::get<Is>(tp),
                                           min_size = std::get<Is>(tp).size())
                                        : 0u), ...);

   return var_ret;
 }

template <typename ... Ts>
auto getLess (std::tuple<Ts...> const & tp)
 { return getLessHelper(tp, std::index_sequence_for<Ts...>{}); }

int main ()
 {
   std::vector<int>     vi {1, 2, 3};
   std::vector<double>  vd {1.0, 2.0};
   std::vector<float>   vf {1.0f};

   auto gl = getLess(std::make_tuple(vi, vd, vf));

   std::cout << std::visit([](auto const & v){ return v.size(); }, gl)
       << std::endl; // print 1, the size() of vf
 }

【讨论】:

  • 一旦你有了索引,你就可以得到向量,当然,但确实需要一些类型擦除——可能通过访问者和变体。可能需要重新审视设计要求。
  • @LightnessRacesinOrbit 你能澄清一下如何做到这一点吗?我确实得到了索引。
  • @LightnessRacesinOrbit - 是的:std::variant 在某种意义上是一种将所有类型一起返回的方式。但是需要 C++17 并且这个问题被标记为 C++11。
  • 我刚升级到 g++ 6.5,所以有部分 c++17 支持,但不幸的是 std::variant 不支持。
  • 我可以使用 boost,所以如果我必须使用它比我之前使用它的程度更高。
【解决方案2】:

假设我们的类型包中没有重复项,或者a way of removing them,以及std::index_sequence 中的C++11 backport

#include <map>
#include <tuple>
#include <functional>
#include <iostream>

#include <boost/variant>
#include <future/index_sequence>

namespace detail {
    template<typename Variant, typename Tuple, std::size_t... Is>
    std::map<std::size_t, std::function<Variant(const Tuple &)>> from_tuple_map(index_sequence<Is...>)
    {
        return { { Is, [](const Tuple & tup){ return std::get<Is>(tup); } }... };
    }

    struct GetSize
    {
        template<typename T>
        std::size_t operator()(const std::vector<T> & vec){ return vec.size(); }
    }; // becomes C++14 generic lambda
}

template<typename... Ts>
boost::variant<Ts...> from_tuple(const std::tuple<Ts...> & tup)
{
    auto map = detail::from_tuple_map<boost::variant<Ts...>, std::tuple<Ts...>>(make_index_sequence<sizeof...(Ts)>());
    auto get_size = GetSize{};
    std::size_t best = 0;
    std::size_t len = visit(get_size, map[best](tup));
    for (std::size_t trial = 1; trial < sizeof...(Ts); ++trial)
    {
        std::size_t trial_len = visit(get_size, map[trial](tup));
        if (trial_len > len)
        {
            best = trial;
            len = trial_len;
        }
    }
    return map[best](tup);
}

int main()
{
    auto x = from_tuple(std::make_tuple(std::vector<int>({1,2}), std::vector<double>({1.0, 2.0, 3.0})));
    visit([](const auto & a){ std::cout << a[1]; }, x);
}

See it live (using C++17 compiler)

【讨论】:

  • @caleth 谢谢你的回答。地图技巧很棒,不幸的是它不适用于 g++ 6.5(编译器错误)。我会尝试升级到 7.4 看看它是否得到修复。
猜你喜欢
  • 2011-03-27
  • 2018-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多