【问题标题】:Mapping opaque arrays to function arguments using variadic templates and typelists使用可变参数模板和类型列表将不透明数组映射到函数参数
【发布时间】:2013-04-01 17:42:02
【问题描述】:

TLTR:我想根据存储在可变参数模板列表中的索引定义的特定顺序将模板容器中的一些数组映射到函数的参数(我想不出一种更简单的方法来定义问题)。

数组使用void* 存储,但数组和函数参数之间的类型安全由辅助类保证。同一个帮助类必须扩展给定的参数包,获取适当的数组,将它们绑定到函数指针并调用函数。这就是我卡住的地方。

detail:对于冗长的问题和无法编译的发布代码,我提前道歉,但我尽量简洁。

问题在于将容器的正确成员映射到函子对象。容器有一个typelist定义的数组列表,其实现类似于this one

为简单起见,我们假设定义了类型列表帮助器对象TLAlg::length<TL>TLAlg::TypeAt,并允许用户分别访问类型列表的长度和第N 个类型。

容器类为类型列表(称为字段)中的每种类型分配一个数组,并存储一个指向这些缓冲区的不透明指针。实现类型安全的 getter 以访问特定的字段索引。其实现如下:

// container class, stores an array for each type in the typelist
template<class TL>
class Volume {
public:
    // list of opaque buffers
    void *opaque_buffers[TLAlg::length<TL>::value];

    template<int size>
    Volume(const size_t (&dim)[size]){
        // each opaque_buffers is initialized here
        ...
    }

    // getters are using the field index for type-safety
    template <int index> typename
    TLAlg::TypeAt<TL, index>::Result &
    get(const std::initializer_list<size_t> &position);
};

我们想要实现一个Functor 对象,它将使用类型列表的特定子集在卷上应用给定的函数。用户不是直接操作数组,而是给出他想要访问的字段的索引列表和要应用的函数的指针。函子对象负责设置正确的参数。

为了提高类型安全性,我们将它们分成两个列表:只读和读/写(读取const 而不是const)。给定的函数原型必须与仿函数对象的定义一致:代码只有在给定的函数指针与参数定义完全匹配时才能编译,所以我们不必担心类型不匹配。函子的实现是:

template<typename TL, class T1, class T2> struct Functor{};
template< typename TL,
          template<size_t...> class T1, size_t... A1, // read only arguments
          template<size_t...> class T2, size_t... A2  // read-write arguments
        >
struct Functor< TL, T1<A1...>, T2<A2...> >{
    // type of the function pointer
    typedef void (*Type)(const typename TLAlg::TypeAt<TL, A1>::Result* ... ,
                               typename TLAlg::TypeAt<TL, A2>::Result* ...);

    Functor(Volume<TL> &v, Type f): f(f){
        // At this point we have everything we need: the volume, the function
        // and the list of arguments, but how to combine them all?

        // maybe some magic here to map the opaque pointers to the arguments?
    }

    void operator()(){
        // or maybe here?
    }
}

如你所见,functor此时并没有做任何事情,因为我不知道如何将这两个参数包映射到容器数组,并将它们绑定到函数指针...

为清楚起见,下面是仿函数类的使用示例:

// main Typelist
typedef Typelist<float, Typelist<double, Typelist<int, NullType>>> Pixel;

// function we want to apply: reads first and last field of the list and updates second
void foo(const float  *f1, 
         const int    *f3,
               double *f2){}

// helper class to store the parameter packs
template<size_t ... T> struct Pack {};

int main(){
    // volume declaration
    Volume<Pixel> volume((size[]){1024,1024});

    // delare typesafe functor
    Functor<Pixel,     // typelist
            Pack<0,2>, // list of read-only fields
            Pack<1>    // list of read-write fields
           > apply_foo(volume, foo);

    apply_foo(); // <- this does nothing at the moment
}

我尝试使用std::forwardstd::bind 玩了很长时间,但我还没有找到正确的解决方案。可以考虑将类型列表替换为 std::tuple,但最好保留当前定义。

这段代码可能看起来很奇怪而且不必要地复杂,但它是一个非常简化的大型框架版本,使用这些类是有意义的。

任何帮助将不胜感激。

澄清 Yakk 的回答:

我确实需要一个类型列表,因为我在其中做了更多的魔法,例如列表中的每个元素都可以是一个元组而不是单个类型来关联一个名称。这允许整洁的代码,例如:

typedef MakeTypeList((float,  p),
                     (float,  bnd),
                     (float,  gosa)) HimenoFields;

// I can now use aliases, and the declaration order does not matter in the code.
// here is an access to the second field:
volume.get<HimenoFields::bnd>({0,0,0});

你可以想象这如何与我想用函子实现的那种操作很好地结合起来。

其次,我明白你为什么对 getter 感到困惑。正如我最初所说,这是一个非常简化的代码版本,尽管问题很长。在实际程序中,体积是多维的,要么在单个数组中展平,要么在多维数组中分配,这就是 getter 需要完整坐标的原因。这些 getter 有多种不同参数的实现。

最后,Functor 不需要知道将函数应用到哪个元素,因为它自己控制迭代空间并应用预定义的骨架(即模板、波前...)。为了简单起见,我再次省略了。

【问题讨论】:

  • 如果我错了,请纠正我,但我的印象是Functor&lt;Pixel, Pack&lt;0,2&gt;, Pack&lt;1&gt;&gt;::Typefoo 的签名不太相似。即兴将std::tuple作为类型列表,可以看到what GCC has to say on the topic
  • 很好看,我的错。我还有一些代码可以重新调整参数,而且我的复制粘贴速度有点太快了。 const 在我给出的示例中打包在一起。我相应地编辑了foo

标签: c++ c++11 variadic-templates stdbind


【解决方案1】:

首先,我会重写你的type_list

template<typename... Ts>
struct type_list {};

使用可变类型而不是您的 18 参数破解。写type_at&lt;n&gt;::typeindex_of&lt;T&gt;::value 并不难。这些和基于对的TypeList 之间的映射也不难。

template<typename list>
struct make_TypeList;

template<typename T0, typename T1, typename...Ts>
struct make_TypeList<type_list<T0, T1, Ts...>> {
  typedef typename make_Typelist< type_list<T1, Ts...> >::type tail;
  typedef TypeList< T0, tail > type;
};
template<typename T0>
struct make_TypeList<type_list<T0>> {
  typedef TypeList< T0, NullType > type;
};
template<>
struct make_TypeList<type_list<>> {
  typedef NullType type;
};

如果你真的需要它。使用非类型列表是有原因的,但您没有展示任何理由。

建立一个编译时类型索引的集合有点棘手,但如果你传入一个上限,你就可以做到。目标是创建一个序列:

template<size_t... s>
struct seq {};

如果您在编译时获取这些索引,这会更容易。一旦你有了这个序列,并且你有了type_at,你就可以编写一个类似这样的调用函数:

template<size_t... s, typename... Ts>
void evaluate( seq<s...> unused, void(*func)(Ts*... ts) ) {
  func( &get_at<s>()... );
}

我们将序列直接解压缩到函数调用中。碰巧,有问题的序列通常只是0,1,2,3,4,...,n-1,可以很容易地生成:

template<size_t max, size_t... s>
struct make_seq:make_seq<max-1, max-1, s...> {};
template<size_t... s>
struct make_seq<0, s...> {
  typedef seq<s...> type;
};

要明确:operator() 在执行make_seq&lt;sizeof...(Ts)&gt;::type() 之后调用辅助函数,将其传递给辅助函数,然后调用func( &amp;get_at&lt;s&gt;(/*n maybe?*/)... ),并且 bob 是你的叔叔

让我感到困惑的是:

// getters are using the field index for type-safety
template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get(const std::initializer_list<size_t> &position);

我不知道为什么需要const std::initializer_list&lt;size_t&gt; &amp;position,或者至少为什么你没有:

template <int index> typename
TLAlg::TypeAt<TL, index>::Result &
get_at(size_t n);

如果您的 Volume 是多个类型的数组,这让我认为您的仿函数中的 operator() 缺少“我应该将此仿函数应用于哪个索引”。

但我强烈怀疑“制作一组索引,调用一个辅助函数,这样你就可以深入了解,然后在函数调用中展开包”是你缺少的技巧。

【讨论】:

  • 非常感谢您的详尽回答。我编辑了我的帖子以澄清你提到的一些观点。如果我能设法实现它,我今天会尝试并接受你的回答;)
  • @Thibaut type_list&lt; int, type_list&lt; bool, double &gt;, type_list&lt; foo &gt;, std::string &gt; 允许完全结构化的列表列表,就像 TypeList 一样。唯一的区别是 TypeList 是基于树的类型结构(如 lisp “列表”),而 type_list 是基于线性的类型结构,并且 C++ 支持“迭代”和“解包” " 基于线性的类型结构。另一方面,如果您的类型列表长度接近 1000,C++ 模板元编程中的递归限制意味着最终需要平衡的基于二叉树的类型集合。
  • 好吧,我想我明白你的意思了。但是字段的索引存储在可变参数模板中,除了我的称为Pack 而不是seq,因此我可以轻松扩展它。类型列表只是为了类型安全。你是说我应该用平面列表替换树类型列表,以便将类型列表与索引列表“对齐”?
  • @thibaut 我是说平面 type_list 在 C++11 中通常比基于树的列表更容易操作,并且有很多内置支持——在 C+ 中+03,基于树的列表,这是处理任意长度列表的唯一方法。
猜你喜欢
  • 2023-03-24
  • 2021-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多