模板类型定义?
Template typedef?
在外部 API 中,我定义了结构:
Foo1
、Foo4
、Foo8
、Foo16
现在我需要定义四个函数:
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>
以便它专门用于 Foo1
、Foo4
、Foo8
, Foo16
可能吗?
我知道我可以创建一个模板结构:
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);
}
您可以使用您专门针对每种类型 Foo1
、Foo4
、… 的辅助结构 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 模板专门化为 Foo1
、Foo4
、Foo8
和 Foo16
:
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);
}
在外部 API 中,我定义了结构:
Foo1
、Foo4
、Foo8
、Foo16
现在我需要定义四个函数:
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>
以便它专门用于 Foo1
、Foo4
、Foo8
, Foo16
可能吗?
我知道我可以创建一个模板结构:
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);
}
您可以使用您专门针对每种类型 Foo1
、Foo4
、… 的辅助结构 bar_trait
。然后,这些特化可以保存各种值,然后您可以在 bar
函数中使用这些值来控制代码流。
这样 bar
可以接受您为其创建 bar_trait
特化并支持您在 bar
.
这样做的好处是:
- 可以在编译时求值,不会有运行时错误
- 你可以在不接触
bar
的情况下专攻 - 您可以在其他可能需要知道
FooN
大小的地方重复使用bar_trait
,从而减少代码重复。 - 不需要 marcos
- 模板参数推导仍然有效
bar_trait
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 模板专门化为 Foo1
、Foo4
、Foo8
和 Foo16
:
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);
}