将“类型参数”与“非类型参数”(或相反)专门化

Specializing "type argument" with "non-type argument" (or the other way around)

本文关键字:类型参数 专门化 非类型参数      更新时间:2023-10-16

我想实现的目标通过下面的玩具示例进行了演示。

#include <cstddef>
template<size_t ElementSize>
class Buffer
{
public:
    char buffer[ElementSize];
};
template<typename T>
class Buffer<sizeof(T)>
{
public:
    char buffer[sizeof(T)];
};
int main()
{
    Buffer<4> b1;   // buffer with 4 bytes
    Buffer<int> b2; // buffer with space for "int"
}

这段代码显然无法编译:

$ g++ test.cpp 
test.cpp:12:7: error: template argument ‘sizeof (T)’ involves template parameter(s)
 class Buffer<sizeof(T)>
       ^
test.cpp: In function ‘int main()’:
test.cpp:22:12: error: type/value mismatch at argument 1 in template parameter list for ‘template<long unsigned int ElementSize> class Buffer’
  Buffer<int> b2; // buffer with space for "int"
            ^
test.cpp:22:12: error:   expected a constant of type ‘long unsigned int’, got ‘int’
test.cpp:22:16: error: invalid type in declaration before ‘;’ token
  Buffer<int> b2; // buffer with space for "int"

有什么方法可以让我拥有这种模板的两个专用化 - 一个使用以字节为单位的显式大小(非类型参数),另一个从类型T(类型参数)中获取大小(带sizeof())?我对不需要两个具有不同名称或任何#define宏的单独模板的解决方案感兴趣。

我试图以"反向"方式实现它 - 将 <typename T> 作为主模板,并使用size_t作为专用化模板(将char[sizeof(T)]std::aligned_storage<...>::type传递给主模板),但这也失败了。

不,你不能。一旦我们有了template <size_t Size> class Buffer,我们就非常有限地限制了我们能做的专业化。即 §14.5.5/8.1:

部分专用的非类型参数表达式不得涉及 部分专用化,除非参数表达式是简单标识符[示例:

template <int I, int J> struct A {};
template <int I> struct A<I+5, I*2> {}; // error
template <int I, int J> struct B {};
template <int I> struct B<I, I> {}; // OK

- 结束示例 ]

因此,我们唯一能做的部分专业化是:

template <size_t I> class Buffer<I> { .. };

违反§14.5.5/8.3

专业化的参数列表

不应与主模板的隐式参数列表相同。

在另一个方向,我们违反了 14.5.5/8.4:

专业化

应比主模板更专业化

绝不

是比T更专业的size_t.

您可以做的只是创建一个别名:

template <size_t ElementSize>
class Buffer {
    char buffer[ElementSize];
};
template <typename T>
using BufferFromType = Buffer<sizeof(T)>;    
// or, if not C++11, another type
template <typename T>
class BufferFromType : public Buffer<sizeof(T)> { };

类模板不能重载。如果类模板Buffer采用类型 size_t 的非类型参数,则每次编写Buffer<thing>时,thing都必须是有效的非类型参数;它不能是一种类型。反之亦然 - 如果Buffer采用类型参数,那么每次编写Buffer<thing> thing时都必须是一个类型。

部分专用

化的模板参数始终在部分专用化匹配期间推导;您永远不能显式指定它们。

但是,函数模板可能会重载。所以你可以写重载make_buffer s并使用auto

template<class T> Buffer<sizeof(T)> make_buffer() { return {}; }
template<size_t Size> Buffer<Size> make_buffer() { return {}; }
auto buffer1 = make_buffer<int>();
auto buffer2 = make_buffer<42>();

你可以使用decltype来实现统一但折磨人的语法:

decltype(make_buffer<int>()) buffer1;
decltype(make_buffer<42>()) buffer2;

然后,您可以为此轻松编写宏。