C++ − 单个参数包是否可以在单个表达式中多次扩展?

C++ − Can a single parameter pack be expanded more than once in a single expression?

提问人:Thibault de Villèle 提问时间:11/15/2023 最后编辑:Thibault de Villèle 更新时间:11/16/2023 访问量:93

问:

我有一个函数,它采用 3 个模板参数:两种类型和一个整数常量(用于特征存储要求)。它看起来像以下模板:

template <typename VertexType, typename IndexType, Eigen::StorageOptions Options>
void my_big_function(/* 3 Eigen parameters depending on template args... */) {
    /* Do stuff with matrices... */
}

而且由于这个函数需要绑定到具有多个模板“三元组”的 python,我认为这可能是模板参数包的一个很好的用法。

我想实现以下目标:

// binds to python :
// - my_big_function<float, int, ColMajor>(...)
bind_my_big_function<float, int, Eigen::ColMajor>(m); // (1)
// binds to python :
// - my_big_function<float, int, ColMajor>(...)
// - my_big_function<float, int, RowMajor>(...)
// - my_big_function<double, int, RowMajor>(...)
bind_my_big_function<float, int, Eigen::ColMajor, float, int, Eigen::RowMajor, double, int, Eigen::RowMajor>(m); // (2)

哪里很简单:bind_my_big_function

template <typename MatVertexType, typename MatIndexType, Eigen::StorageOptions MatOptions, typename...RemainingArgs>
void bind_my_big_function(pybind11::module_& m) {
    /* bind function taking MatVertexType, MatIndexType & MatOptions and go onto binding other variants of my_big_function */
}

虽然我肯定可以生成一些处理大小写 (1) 的代码,但我无法弄清楚如何编写处理大小写 (2) 的代码。我得到的最接近的是这样的:

template <typename MatVertexType, typename MatIndexType, Eigen::StorageOptions MatOptions>
void bind_my_big_function(pybind11::module_& m) {
    m.def("my_big_function_in_python", &my_big_function<MatVertexType, MatIndexType, MatOptions>, /* python arguments...*/);
}

template <
    typename MatVertexType, typename MatIndexType, Eigen::StorageOptions MatOptions,
    typename...RemainingArgs, typename = typename std::enable_if_t<sizeof...(RemainingArgs) != 0>
>
void bind_my_big_function(pybind11::module_& m) {
    m.def("my_big_function_in_python", &my_big_function<MatVertexType, MatIndexType, MatOptions>, /* python arguments...*/);
    bind_my_big_function<RemainingArgs...>(m);
}

参数包应该以这种方式使用吗?我能找到的唯一使用它们的例子是:

  • 使用模板化函数参数,
  • 一次使用单个包扩展(调用函数只需要一种类型和一个可变参数模板包)
C++ 模板 variadic-templates

评论

0赞 Red.Wave 11/15/2023
不能将 None 类型模板参数 () 捕获为 。Eigen::StorageOptions MatOptionstypename

答:

0赞 463035818_is_not_an_ai 11/15/2023 #1

不确定我是否理解这个问题,但如果你想实例化一些给定的东西,你可以包装函数模板,只需要实例化它:foo<Ts...,Ts...,Ts...>Ts...

template <typename ...Ts>
void foo() {};

template <typename ...Ts>
void foo_expand() {
    foo<Ts...,Ts...,Ts...>();
};

评论

0赞 Thibault de Villèle 11/15/2023
问题似乎源于我混合了类型名和定义良好的类/类型:编译器在我的 OP 中的第二个用例中抛出错误,因为枚举与类型名匹配。似乎参数包实际上不是一次扩展 3 个项目,而是一次扩展一个项目。Eigen::StorageOption
0赞 Thibault de Villèle 11/15/2023 #2

为了让其他人尝试做同样的事情,这就是我最终所做的。

我将模板“三元组”的每个参数打包到类似元组的空结构中,并使用对元组的递归调用执行包扩展。下面是结果代码的粗略骨架:

template <typename... Types> struct type_tuple {};
template <Eigen::StorageOptions...> struct eigen_storage_tuple {};

template <typename VertexType, typename IndexType, Eigen::StorageOptions Options>
constexpr void bind(pybind11::module_& m) {
    /* Bind the current template of my_big_func() given params above: */
    m.def(/* ... */);
}

template <
    typename VertexType, template <typename> class VertexTypeTuple,
    typename IndexType, template <typename> class IndexTypeTuple,
    Eigen::StorageOptions Options, template <Eigen::StorageOptions> class OptionTypeTuple
>
constexpr void bind_recursively(
    pybind11::module_& m,
    const VertexTypeTuple<VertexType> vertex_types,
    const IndexTypeTuple<IndexType> index_types,
    const OptionTypeTuple<Options> option_type
) {
    bind<VertexType, IndexType, Options>(m);
}

template <
    typename VertexType, typename... VertexTypes, template <typename, typename...> class VertexTypeTuple,
    typename IndexType, typename... IndexTypes, template <typename, typename...> class IndexTypeTuple,
    Eigen::StorageOptions Option, Eigen::StorageOptions... Options, template <Eigen::StorageOptions, Eigen::StorageOptions...> class OptionsTypeTuple
>
constexpr void bind_recursively(
    pybind11::module_& m,
    const VertexTypeTuple<VertexType, VertexTypes...> vertex_types,
    const IndexTypeTuple<IndexType, IndexTypes...> index_types,
    const OptionsTypeTuple<Option, Options...> options
) {
    VertexTypeTuple<VertexType> current_vertex_type;
    IndexTypeTuple<IndexType> current_index_type;
    OptionsTypeTuple<Option> current_option;

    VertexTypeTuple<VertexTypes...> remaining_vertex_type;
    IndexTypeTuple<IndexTypes...> remaining_index_type;
    OptionsTypeTuple<Options...> remaining_option;

    bind_recursively(m, current_vertex_type, current_index_type, current_option);
    bind_recursively(m, remaining_vertex_type, remaining_index_type, remaining_option);
}

函数调用中的所有元组参数仅用于重载解析和包扩展。

由于每个参数包在递归绑定函数中都由“具体”类型包围,因此我能够遍历所有三个包并在编译时生成所有调用。下面是如何调用该函数并解析所有调用的示例:pybind11::module_::def()

constexpr type_tuple<
    float, float, float, float, /* For col major types */
    float, float, float, float, /* For row major types */
    double, double, double, double, /* For col major types */
    double, double, double, double  /* For row major types */
> remove_unused_vertices_vertex_types;
constexpr type_tuple<
    std::int32_t, std::uint32_t, std::int64_t, std::uint64_t, /* For col major types */
    std::int32_t, std::uint32_t, std::int64_t, std::uint64_t, /* For row major types */
    std::int32_t, std::uint32_t, std::int64_t, std::uint64_t, /* For col major types */
    std::int32_t, std::uint32_t, std::int64_t, std::uint64_t  /* For row major types */
> remove_unused_vertices_index_types;
constexpr eigen_storage_tuple<
    Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor,
    Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor,
    Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor, Eigen::StorageOptions::ColMajor,
    Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor, Eigen::StorageOptions::RowMajor
> remove_unused_vertices_storage_orders;

bind_recursively(
    cull_module,
    remove_unused_vertices_vertex_types,
    remove_unused_vertices_index_types,
    remove_unused_vertices_storage_orders
);

/* Will bind all combinations of the vertex, index, and storage order types given above. */
1赞 ecatmur 11/15/2023 #3

您还不能在单个包中捕获混合类型 (, ) 和非类型 () 模板参数,因为从 C++23 开始,包必须是单一类型:类型 (/)、或值(具体或 )。MatVertexTypeMatIndexTypeMatOptionstypenameclasstemplateauto

有几种可能的解决方案:

  1. 使用 例如,将非类型模板参数提升为类型空间,以便它可以与类型参数一起传递到类型包中;std::integral_constant<Eigen::StorageOption>
  2. 将相关参数的每三元组组合成一种类型,例如 (这是 AOS 选项);template<class MatVertexType, class MatIndexType, Eigen::StorageOption MatOptions> struct MatParameters;
  3. (如您的解决方案 https://stackoverflow.com/a/77488183/567292)通过三个均质包装,例如 对于类型包,(在类型空间中)或(在值空间中)对于值包(这是选项 2 的 SOA 变体);std::tuplestd::integer_sequencestd::array
  4. 等待“通用模板参数”,这是为 C++26 提出的。

评论

0赞 Thibault de Villèle 11/16/2023
我已经找到了一个解决方案,我将其发布在答案中,这基本上是您提出的 (3.) 的不同版本。我有三个类似元组的结构,由于递归函数调用,我对其进行了迭代。
0赞 Thibault de Villèle 11/16/2023
不过,仍然存在几个问题:如果我使用一个常量并只获取模板包中的类型名称,我仍然无法一次迭代 3 个项目,对吧?如果包从子序列的第二项开始扩展,我是否必须提供部分专用化,或者使用 3 个模板参数和一个参数包提供单个部分专用化就足够了?std::integer_constant
0赞 ecatmur 11/16/2023
@ThibaultdeVillèle是的,要一次使用多个包元素,您必须使用递归或某种等效技术。检查是否递归使用的单个函数模板可能是最干净的选择。if constexpr (sizeof...(Rest) != 0)