首页 > 解决方案 > Transpose a container of structs

问题描述

Let we have a struct Record{uint8_t x, y;};, a container Container<Record> of structs and a struct Transposed{Container<uint8_t> x,y};. Container c is a template which first arg is a type of value, all of the rest args have default values. For example it can be a std::vector (the rest args are types) or std::span (the rest arg is a value). The template should work with all of them. Also we may like to pass tne rest of template arguments to underlying template.

How we can get the Transposed from the container using templates?

I have tried variadic templates,

#include <iostream>
#include <vector>

template <typename value_type=uint8_t> struct Record{
    value_type x, y;
};

template<typename value_type, typename value_type2=uint8_t> class ContainerA: public std::vector<value_type>{
    value_type2 b=1u;
};

template<typename value_type, uint8_t int_value=1u> class ContainerB: public std::vector<value_type>{};

template<typename value_type, template <typename ...> typename container_type> class Transposed{
    container_type<value_type> x, y;

    public:
        Transposed(container_type<Record<value_type>> & recs){
            x.reserve(recs.size());
            y.reserve(recs.size());
            x.resize(recs.size());
            y.resize(recs.size());
            size_t i=0;
            for(auto &rec :recs){
                x[i] = rec.x;
                y[i] = rec.y;
                ++i;
            }
        }
};

int main(){
    std::vector<Record<uint8_t>> recsV{
        {1, 2},
        {3, 4}
    };
    Transposed trV{recsV};
    std::cout<<"vec"<<std::endl;

    ContainerA<Record<uint8_t>> recsA{
        {1, 2},
        {3, 4}
    };
    Transposed trA{recsA};
    std::cout<<"A"<<std::endl;

    /*ContainerB<Record<uint8_t>> recsB{
        {1, 2},
        {3, 4}
    };
    Transposed trB{recsB};
    std::cout<<"B"<<std::endl;*/
    return 0;
}

but it seems they cannot match both types and values. Usage of more than 1 variadic template argument is not allowed. Is it a flaw in C++ language or a deliberate design choice and do we need something like any_template_arg keyword or should just specifying 2 variadic arguments of different types be allowed to allow this use case?

标签: c++templatesstlc++17variadic-templates

解决方案


据我所知,没有办法将类型和值(以及模板模板)模板参数匹配在一起。我已经搜索了很长时间。

所以我看不到一种方法可以简单而优雅地制作你想要的东西。

试图回答你的问题

我们如何Transposed使用模板从容器中获取?

我能想象的最好的是声明(没有必要定义它们,它们只在 a 内部使用decltype())几个微不足道的函数,如下所示

template <typename VT,
          template <typename...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename VT,
          template <typename, auto...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

第一个是在容器接受类型(和案例)的可变参数列表Record时删除部分;第二个是容器接受(在类型参数之后)一个或多个值(大小写)。CTstd::vectorContainerACTContainerB

显然,这并没有涵盖所有可能的情况,但是声明其他extract_func()函数来涵盖其他情况是微不足道的。

现在您可以声明Tranposed如下

template <typename T,
          typename CT = decltype(extract_func(std::declval<T>()))>
class Transposed

请注意,现在Transposed接受泛型类型T,但仅当T类型匹配(作为参数)extract_func()声明时才启用 SFINAE。

Transposed正文中,您可以使用和CT声明构造函数的参数。xyT

下面是一个完整的编译示例

#include <iostream>
#include <vector>

template <typename value_type=std::uint8_t>
struct Record
 { value_type x, y; };

template <typename value_type, typename value_type2=std::uint8_t>
class ContainerA : public std::vector<value_type>
 { value_type2 b=1u; };

template <typename value_type, std::uint8_t int_value=1u>
class ContainerB : public std::vector<value_type>
 { };

template <typename VT,
          template <typename...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename VT,
          template <typename, auto...> typename CT>
CT<VT> extract_func (CT<Record<VT>>);

template <typename T,
          typename CT = decltype(extract_func(std::declval<T>()))>
class Transposed
 {
   private:
      CT x, y;

   public:
      Transposed (T & recs)
       {
         x.reserve(recs.size());
         y.reserve(recs.size());
         x.resize(recs.size());
         y.resize(recs.size());
         std::size_t i=0u;
         for(auto &rec :recs){
            x[i] = rec.x;
            y[i] = rec.y;
            ++i;
         }
       }
 };

int main ()
 {
   std::vector<Record<std::uint8_t>> recsV { {1, 2}, {3, 4} };

   Transposed trV{recsV};

   std::cout<<"vec"<<std::endl;

   ContainerA<Record<std::uint8_t>> recsA { };

   Transposed trA{recsA};

   std::cout<<"A"<<std::endl;

   ContainerB<Record<std::uint8_t>> recsB { };

   Transposed trB{recsB};

   std::cout<<"B"<<std::endl;
 }

推荐阅读