用于转换多个参数的模板函数

Template function for casting multiple number of params

我正在尝试编写一个模板函数,它将根据其模板参数执行一组 dynamic_casts()。我有以下说明:

class FooData
{
public:
    virtual ~FooData() {};
};
class DerivedFooData: public FooData{};
class Other: public FooData{};

void bar(DerivedFooData* d1, DerivedFooData* d2) {}
void bar(DerivedFooData* d1, Other*, DerivedFooData* d2, Other*, Other*) {}


int main()
{
    DerivedFooData d1,d2;
    std::vector<FooData*> container1{&d1, &d2};
    std::vector<FooData*> container2{&d1, &d2};

    // I want bar to be called with container1[0] cast to DerivedFooData and container2[1] cast to DerivedFooData 
    // the first two template params are each container size

    foo<1, 1, DerivedFooData, DerivedFooData>(container, container2);

    // I want bar to be called with
    // container1[0] cast to DerivedFooData
    // container1[1] cast to Other
    // container2[0] cast to DerivedFooData
    // container2[1] cast to Other
    // container2[2] cast to Other
    foo<2, 3, DerivedFooData, Other, DerivedFooData, Other, Other>(container, container2);
}

我可以手动创建其中一些:

template <int N, int M, typename U, typename V>
void foo(const std::vector<FooData*>& input, const std::vector<FooData*>& output) 
{
    bar(dynamic_cast<U*>(input[0]), dynamic_cast<V*>(output[0]));
}

template <int N, int M, typename U, typename V, typename W, typename X, typename Y>
void foo(const std::vector<FooData*>& input, const std::vector<FooData*>& output) 
{
    bar(dynamic_cast<U*>(input[0]), dynamic_cast<V*>(input[1]), dynamic_cast<W*>(output[0]), dynamic_cast<X*>(output[1]), dynamic_cast<Y*>(output[2]));
}

但我不知道如何以通用方式指定 NM 的所有组合。我假设可变参数模板会出现在某个地方,但我需要一些指导。

任何模板递归背后的基本思想都是一次处理一个参数,在函数上递归并删除一个输入类型参数,然后在模板参数列表为空时终止。

处理两个可变类型列表的一种常见方法是定义一个 "pack" 您可以专门研究的类型,其中包采用可变数量的模板参数。这使您能够轻松分离多组可变类型参数。

在这里,我通过声明 type_pack 没有任何实现来演示这个例子(这很好,我们只将它用作一个类型,我们从不实例化它)然后我声明 [=12= 的多个特化] 旨在:

  • 从两个列表中剥离第一个类型并执行 dynamic_cast
  • 通过删除相关包类型的第一个类型参数来确定下一步。
  • 当第一个包变空时过渡到第二个包。
  • 将处理(转换)的参数传递到下一步。
  • 最后,当两个包都为空时,使用计算出的参数调用 bar()
template <typename...>
struct type_pack;

// Base declaration that we will specialize.
template <int, int, typename, typename>
struct foo_fn;

// Specialization handling when the first pack is not empty.
template <int OffsetA, int OffsetB, typename THead, typename... T1, typename... T2>
struct foo_fn<OffsetA, OffsetB, type_pack<THead, T1...>, type_pack<T2...>> {
    template <typename... Args>
    static void f(foovec_t const & input, foovec_t const & output, Args && ... args) {
        return foo_fn<
            OffsetA + 1,
            OffsetB,
            type_pack<T1...>,
            type_pack<T2...>
        >::f(input, output, std::forward<Args>(args)..., dynamic_cast<THead *>(input[OffsetA]));
    }
};

// Specialization handling when the first pack is empty and the second
// pack is not empty.
template <int OffsetA, int OffsetB, typename THead, typename... T>
struct foo_fn<OffsetA, OffsetB, type_pack<>, type_pack<THead, T...>> {
    template <typename... Args>
    static void f(foovec_t const & input, foovec_t const & output, Args && ... args) {
        return foo_fn<
            OffsetA,
            OffsetB + 1,
            type_pack<>,
            type_pack<T...>
        >::f(input, output, std::forward<Args>(args)..., dynamic_cast<THead *>(output[OffsetB]));
    }
};

// Specialization handling the terminating case (all packs empty).
template <int OffsetA, int OffsetB>
struct foo_fn<OffsetA, OffsetB, type_pack<>, type_pack<>> {
    template <typename... Args>
    static void f(foovec_t const &, foovec_t const &, Args && ... args) {
        bar(std::forward<Args>(args)...);
    }
};

// Helper type to provide the two initial integer values.
template <typename, typename>
struct foo;

template <typename... T1, typename... T2>
struct foo<type_pack<T1...>, type_pack<T2...>> {
    static void f(foovec_t const & input, foovec_t const & output) {
        foo_fn<0, 0, type_pack<T1...>, type_pack<T2...>>::f(input, output);
    }
};

您可以在第二个示例中将其称为 foo<type_pack<DerivedFooData, Other>, type_pack<DerivedFooData, Other, Other>>::f(container, container2)。请注意,您不必提供任何尺寸;这些是根据每包的大小推断的。

参见 this demo 并注意类型不匹配的指针参数作为空值出现。

我不会尝试定义 bar(),因为我假设您已经这样做了,或者知道如何去做。我示例中的 bar() 只接受特定的指针类型(为了测试转换是否正确执行)。

此代码仅使用 C++11 功能。


请注意,std::forward 不是绝对必要的,因为转换值始终是指针。但是,在转发可变大小的参数列表时养成使用它的习惯是很好的。如果值很大 strings/vectors 那么每一步转发都会消除大量无用的复制。

一点也不优雅(并且不确定确切的索引)但是......下面的东西(假设你可以使用 C++14)应该可以工作(如果我理解正确的话你想要什么)

template <std::size_t Dim1, typename ... Ts, std::size_t ... Is>
void foo_helper (std::index_sequence<Is...>, std::vector<FooData*> inV,
                 std::vector<FooData*> outV)
 { bar( dynamic_cast<Ts*>(Is < Dim1 ? inV[Is] : outV[Is-Dim1])... ); }

template <std::size_t Dim1, std::size_t Dim2, typename ... Ts>
void foo (std::vector<FooData*> inV, std::vector<FooData*> outV)
 { foo_helper<Dim1, Ts...>
      (std::make_index_sequence<Dim1+Dim2>{}, inV, outV); }

我知道 C++20 对您来说太新了,但是为了好玩,我将向您展示如何使用新的 C++20 lambda 模板功能来避免使用辅助函数

// from C++20: foo_helper() isn't needed anymore
template <std::size_t Dim1, std::size_t Dim2, typename ... Ts>
void foo (std::vector<FooData*> inV, std::vector<FooData*> outV)
 { [&]<std::size_t ... Is>(std::index_sequence<Is...>)
    { bar( dynamic_cast<Ts*>(Is < Dim1 ? inV[Is] : outV[Is-Dim1])... ); }
      (std::make_index_sequence<Dim1+Dim2>{}); }

下面是一个完整的编译C++14的例子

#include <vector>
#include <type_traits>


struct FooData { virtual ~FooData() {}; };
class DerivedFooData: public FooData { };
class Other         : public FooData { }; 

void bar (DerivedFooData*, DerivedFooData*) {}
void bar (DerivedFooData*, Other*, DerivedFooData*, Other*, Other*) {}

template <std::size_t Dim1, typename ... Ts, std::size_t ... Is>
void foo_helper (std::index_sequence<Is...>, std::vector<FooData*> inV,
                 std::vector<FooData*> outV)
 { bar( dynamic_cast<Ts*>(Is < Dim1 ? inV[Is] : outV[Is-Dim1])... ); }

template <std::size_t Dim1, std::size_t Dim2, typename ... Ts>
void foo (std::vector<FooData*> inV, std::vector<FooData*> outV)
 { foo_helper<Dim1, Ts...>
      (std::make_index_sequence<Dim1+Dim2>{}, inV, outV); }

int main ()
 {
   DerivedFooData d1, d2, d3;
   std::vector<FooData*> container1 {&d1, &d2};
   std::vector<FooData*> container2 {&d1, &d2, &d3};

   foo<1, 1, DerivedFooData, DerivedFooData>(container1, container2);

   foo<2, 3, DerivedFooData, Other, DerivedFooData, Other, Other>
      (container1, container2);
 }

我更喜欢 "pack type" 语法而不是给出额外的数字,所以

foo<std::tuple<DerivedFooData, Other>, std::tuple<DerivedFooData, Other, Other>>

而不是你的:

foo<2, 3, DerivedFooData, Other, DerivedFooData, Other, Other>

您仍然可以:

template <typename Tuple, std::size_t ... Is1, std::size_t ... Is2, typename ... Ts>
void foo(std::index_sequence<Is1...>, std::index_sequence<Is2...>, Ts&&...args)
{
    foo<std::tuple<std::tuple_element_t<Is1, Tuple>...>,
        std::tuple<std::tuple_element_t<sizeof...(Is1) + Is2, Tuple>...>>(
            std::forward<Ts>(args)...);
}

template <std::size_t N1, std::size_t N2, typename ... Ts>
void foo(const std::vector<FooData*>& input, const std::vector<FooData*>& output)
{
    static_assert(N1 + N2 == sizeof...(Ts), "!");
    foo<std::tuple<Ts...>>(std::make_index_sequence<N1>{},
                           std::make_index_sequence<N2>{},
                           input,
                           output);
}

使用您的语法。

现在使用主助手将每个矢量元素转换为 tuple

template <typename Pack> struct dynamic_cast_as_tuple;

template <typename ...Ts>
struct dynamic_cast_as_tuple<std::tuple<Ts...>>
{
    template <typename T>
    std::tuple<Ts*...> operator ()(const std::vector<T*>& v) const
    {
        return (*this)(v, std::index_sequence_for<Ts...>{});
    }

private:
    template <typename T, std::size_t ... Is>
    std::tuple<Ts*...> operator ()(const std::vector<T*>& v, std::index_sequence<Is...>) const
    {
        return {dynamic_cast<Ts*>(v[Is])...};
    }
};

然后,想要的功能是:

template <typename pack1, typename pack2>
void foo(const std::vector<FooData*>& input, const std::vector<FooData*>& output)
{
    std::apply([](auto*... ps){ bar(ps...); },
               std::tuple_cat(
                   dynamic_cast_as_tuple<pack1>{}(input),
                   dynamic_cast_as_tuple<pack2>{}(output))
        );
}

Demo

std::index_sequence是C++14和std::applyc++17,但可以在C++11中实现。