模板类型定义?

Template typedef?

在外部 API 中,我定义了结构: Foo1Foo4Foo8Foo16

现在我需要定义四个函数:

void bar(Foo1*);
void bar(Foo4*);
void bar(Foo8*);
void bar(Foo16*);

这些函数在迭代 1、4、8 和 16 次的循环中执行相同的操作。

为了避免写4次这些函数,我很乐意用模板来定义它们:

template<unsigned int N> void bar(Foo<N> * foo)
{
    for(unsigned int i=0;i<N;++i)
    {
        //Some critical code that optimizes nicely with SSE, AVX, etc...
    }
}

但是,我不知道如何定义模板 class Foo<N> 以便它专门用于 Foo1Foo4Foo8Foo16

可能吗?

我知道我可以创建一个模板结构:

template<unsigned int N> struct Foo;
template<> struct Foo<1>{ Foo1 f; };
template<> struct Foo<4>{ Foo4 f; };
template<> struct Foo<8>{ Foo8 f; };
template<> struct Foo<16>{ Foo16 f; };

这在功能上与我想要实现的目标相同,但会使 bar 代码有些膨胀,其中将充满 foo.f,并且依赖于来自 [=24= 的转换] 到 Foo<N>*.

I don't know how to define the template class Foo so that it's specialized to Foo1, Foo4, Foo8, Foo16

像这样:

template <int N> struct Foo_impl {};
template <> struct Foo_impl<1 > {using type = Foo1 ;};
template <> struct Foo_impl<4 > {using type = Foo4 ;};
template <> struct Foo_impl<8 > {using type = Foo8 ;};
template <> struct Foo_impl<16> {using type = Foo16;};
template <int N> using Foo = typename Foo_impl<N>::type;

但问题是模板参数推导不适用于这样的别名:

template <int N> void bar(Foo<N> *foo) {}

int main()
{
    Foo<4> x;
    bar(&x); // error: no matching function for call to 'bar'
             // note: candidate template ignored: couldn't infer template argument 'N'
}

要使其正常工作,您必须使用 template <typename T> void bar(T *foo) {} 之类的东西,并使用 static_assert(或其他技巧)将 T 限制为这 4 种类型之一。

你可以这样做:

template <typename T> void bar(T *foo)
{
    constexpr int N =
        std::is_same_v<T, Foo1 > ? 1  :
        std::is_same_v<T, Foo4 > ? 4  :
        std::is_same_v<T, Foo8 > ? 8  :
        std::is_same_v<T, Foo16> ? 16 : throw "Invalid T.";
    // ...
}

此处,throw "Invalid T." 实际上不会在运行时抛出,但如果 T 不是 Foo# 之一,则会导致编译时错误。

对于 运行 你的循环取决于 class,你可以使用以 C++11 开头的常量表达式:

template<typename T>
void bar(T) {
    constexpr unsigned int N = std::is_same<T, Foo4>::value * 4 + std::is_same<T, Foo16>::value * 16; // likewise for Foo1, Foo8
    // ...
}

表达式在编译时求值,因此 N 在编译时也是已知的。如果调用方法时未包含 class,则在该方法的实例化中,N 为 0。您可以在编译时使用 static_assert 进行检查:

static_assert(N > 0, "bar() called with object of invalid type");

将四个 class 放在一起,它看起来像这样:

template<typename T>
void bar(T *a) {
    constexpr unsigned int N = std::is_same<T, Foo1>::value * 1
        + std::is_same<T, Foo4>::value * 4
        + std::is_same<T, Foo8>::value * 8
        + std::is_same<T, Foo16>::value * 16;
    static_assert(N > 0, "bar() called with object of invalid type");

    for( unsigned int i = 0; i < N; ++i) {
        std::cout << i << std::endl;
    }
}

就是这样!

对于在编译时限制函数使用的另一种方法,您可以使用 std::enable_if 将您的专业化范围缩小到四种类型:

#include <type_traits>

template<typename T>
typename std::enable_if_t<(std::is_same<T, Foo4>::value || std::is_same<T, Foo16>::value)>
bar(T *a) {
    // code
}

这使用 return 类型使函数仅在使用允许的类型(Foo4 或 Foo16)实例化时编译。从 C++17 开始,您还可以使用 std::disjunction.

这样的事情怎么样?专门化函数,而不是类型本身:

template<typename FooT, unsigned N>
void bar_impl(FooT f)
{
    for (unsigned int i = 0; i < N; ++i)
    {
        //Do magic!
    }
}

template<typename FooT>
void bar(FooT f);

template<>
void bar<Foo1>(Foo1 f)
{
    bar_impl<Foo1, 1>(f);
}
template<>
void bar<Foo4>(Foo4 f)
{
    bar_impl<Foo4, 4>(f);
}
template<>
void bar<Foo8>(Foo8 f)
{
    bar_impl<Foo8, 8>(f);
}
template<>
void bar<Foo16>(Foo16 f)
{
    bar_impl<Foo16, 16>(f);
}

您可以使用您专门针对每种类型 Foo1Foo4、… 的辅助结构 bar_trait。然后,这些特化可以保存各种值,然后您可以在 bar 函数中使用这些值来控制代码流。

这样 bar 可以接受您为其创建 bar_trait 特化并支持您在 bar.

中应用的表达式的任何类型

这样做的好处是:

  • 可以在编译时求值,不会有运行时错误
  • 你可以在不接触 bar
  • 的情况下专攻 bar_trait
  • 您可以在其他可能需要知道 FooN 大小的地方重复使用 bar_trait,从而减少代码重复。
  • 不需要 marcos
  • 模板参数推导仍然有效
struct Foo1 {};
struct Foo4 {};
struct Foo8 {};
struct Foo16 {};

// create a declaration for bar_trait but no definition so that specializations are required
template<typename T>
struct bar_trait;

// create the specializations for `Foo1`, `Foo4`, …
template<>
struct bar_trait<Foo1> {
    static constexpr const size_t size = 1;
};

template<>
struct bar_trait<Foo4> {
    static constexpr const size_t size = 4;
};

template<>
struct bar_trait<Foo8> {
    static constexpr const size_t size = 8;
};

template<>
struct bar_trait<Foo16> {
    static constexpr const size_t size = 16;
};

// accepts any type for T as long as a specialization for `bar_trait<T>` exists
// and the expressions applied on `foo` are valid
template<typename T> void bar(T * foo)
{
    constexpr const auto N = bar_trait<T>::size;

    for(unsigned int i=0;i<N;++i)
    {
        //Some critical code that optimizes nicely with SSE, AVX, etc...
    }
}

int main()
{
   Foo1 foo1;
   Foo4 foo4;

   bar(&foo1);
   bar(&foo4);
}

您可以声明以下方便的 class 模板,Foo_number_taker:

template<typename> struct Foo_number_taker;

然后,借助下面的宏,DEF_FOO_NUMBER_TAKER:

#define DEF_FOO_NUMBER_TAKER(N) template<> \
                                struct Foo_number_taker<Foo##N> { \
                                   static constexpr unsigned value = N; \
                                }; 

您可以轻松地将上面的 Foo_number_taker class 模板专门化为 Foo1Foo4Foo8Foo16:

DEF_FOO_NUMBER_TAKER(1)
DEF_FOO_NUMBER_TAKER(4)
DEF_FOO_NUMBER_TAKER(8)
DEF_FOO_NUMBER_TAKER(16)

例如,DEF_FOO_NUMBER_TAKER(8) 将扩展为:

template<> struct Foo_number_taker<Foo8> { static constexpr unsigned value = 8; };

因此,它会以某种方式将类型 Foo8 与数字 8 相关联,并且该宏可以防止错误的关联(例如,将 Foo8 与number 4) 从发生。与其余专业类似。

可选地,为了方便起见,您可以定义以下变量模板:

template<typename FooN>
static auto constexpr Foo_number_v = Foo_number_taker<FooN>::value;

最后,您定义 bar() 函数模板以利用上面的这个变量模板找出与模板参数关联的数字 FooN:

template<typename FooN>
void bar(FooN* foo) {
   auto constexpr N = Foo_number_v<FooN>;

   for(unsigned i = 0; i < N; ++i) {
      // ...
   }
}

使用起来很简单:

auto main() -> int {
   Foo1 *f1;
   Foo4 *f4;

   // ...

   bar(f1);
   bar(f4);
}