【问题标题】:Template function for casting multiple number of params用于转换多个参数的模板函数
【发布时间】:2020-01-07 17:35:30
【问题描述】:

我正在尝试编写一个模板函数,该函数将根据其模板参数执行一组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 的所有组合。我认为可变参数模板会出现在某个地方,但我需要一些指导。

【问题讨论】:

  • 你能解释一下NM 参数应该控制什么吗?
  • N 和 M 将是第一个和第二个参数向量的大小(在 foo()s 中输入和输出)
  • FWIW,如果您使用的是dynamic_cast,您通常存在设计缺陷。如果你打算使用它,你真的应该检查返回值以确保它不是nullptr,否则你将面临未定义的行为。
  • 如果NM 是向量的大小,并且必须在编译时知道它们,为什么你使用std::vector 而不是std::array
  • @NathanOliver 同意,在演员阵容中,只是试图保持最小的例子很小:-)

标签: c++ c++11 templates variadic-templates template-meta-programming


【解决方案1】:

一点也不优雅(并且不确定确切的索引)但是......如下(假设你可以使用 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-template 功能来避免使用辅助函数

// 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);
 }

【讨论】:

  • 巧妙使用std::index_sequence,我喜欢。
【解决方案2】:

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

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

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

  • 从两个列表中的一个中剥离第一个类型并执行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&lt;type_pack&lt;DerivedFooData, Other&gt;, type_pack&lt;DerivedFooData, Other, Other&gt;&gt;::f(container, container2)。请注意,您不必提供任何尺寸;这些是从每个包装的大小推断出来的。

请参阅 this demo 并注意类型不匹配的指针参数作为 null 传入。

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

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


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

【讨论】:

    【解决方案3】:

    我更喜欢“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::apply c++17,但可以在 C++11 中实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-25
      • 2014-10-23
      • 2021-03-31
      • 2012-06-16
      • 1970-01-01
      • 2020-09-12
      相关资源
      最近更新 更多