如何确定模板参数是否为默认值
How can I determine if a template parameter is defaulted or not?
我有这个简化的类(省略了许多细节):
template<class T, size_t nChunkSize = 1000>
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
Holder(size_t nSize)
: m_nSize(nSize)
{
}
size_t GetChunkSize()
{
return m_nChunkSize;
}
T* GetChunk(size_t nChunkIndex)
{
// returns the address of the chunk nChunkIndex
return ###;
}
T& operator[](size_t nIndex)
{
// returns the element with index nIndex
return T();
}
};
这个想法是有一个简单的内存管理器来分配大量对象,但如果没有足够的内存将所有对象保存在一个地方,它会将它们分成块并封装所有内容。我知道我应该使用STL,但我有具体的理由这样做。
我希望为用户提供指定块大小并能够获取指向特定块的指针的能力,但前提是他们指定了模板参数,否则我希望在编译时禁用该功能。
我知道编译器应该知道 nChunkSize 是默认的还是用户指定的,但是有没有办法获取该信息并使用它来删除 GetChunk 函数或使其用法不可编译。
例如:
Holder<int, 200> intHolder(5000); // allocates 5000 integeres each chunk holds 200 of them
intHolder[312] = 2;
int* pChunk = intHolder.GetChunk(3); // OK, Compiles
Holder<int> intAnotherHolder(500); // allocates 500 but chunk size is hidden/implementation defined
pChunk = intAnotherHolder.GetChunk(20); // COMPILE ERROR
可以使用具有两个派生类的公共基类:一个专门用于提供size_t
的方案,另一个专门用于未提供派生类的方案:
基础(基本上是你当前的类):
template<typename T, size_t nChunkSize=1000>
class Base
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
Base(size_t nSize)
: m_nSize(nSize)
{
}
size_t GetChunkSize()
{
return m_nChunkSize;
}
T& operator[](size_t nIndex)
{
// returns the element with index nIndex
return T();
}
};
默认(无法调用GetChunk
):
// empty argument list
template<typename T, size_t... ARGS>
class Holder : public Base<T>
{
static_assert(sizeof...(ARGS) == 0, "Cannot instantiate a Holder type with more than one size_t");
using Base<T>::Base;
};
非默认值(具有GetChunk
方法):
template<typename T, size_t nChunkSize>
class Holder<T, nChunkSize> : public Base<T, nChunkSize>
{
using Base<T>::Base;
public:
T* GetChunk(size_t nChunkIndex)
{
// returns the address of the chunk nChunkIndex
return nullptr;
}
};
演示
如果nChunkSize
是类型模板参数,则可以使用默认标记并基于该标记工作。由于它是一个非类型参数,因此可以使用标志值作为默认值,然后在类定义中更正它:
template<class T, size_t nChunkSize = std::numeric_limits<size_t>::max()>
// flag value ^--------------------------------^
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize =
nChunkSize == std::numeric_limits<size_t>::max() ? 1000 : nChunkSize;
//^If the flag value was used, correct it
T* GetChunk(size_t nChunkIndex)
{
//Check if the flag value was used
static_assert(nChunkSize != std::numeric_limits<size_t>::max(),
"Can't call GetChunk without providing a chunk size");
// return the address of the chunk nChunkIndex
}
如果没有传递默认参数,这将使GetChunk
编译失败。当然,如果您将最大size_t
传递给Holder
,那么它将静默地固定到 1000
,但大概您不打算传递那么高的值。
现场演示
我建议使用两个不同的类:如果它们应该有不同的实现,为什么要坚持一个单一的定义?
template<class T, size_t nChunkSize>
class ChunkHolder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
ChunkHolder(size_t nSize) : m_nSize(nSize) {}
size_t GetChunkSize() { return m_nChunkSize; }
// returns the address of the chunk nChunkIndex
T* GetChunk(size_t nChunkIndex) { return nullptr; }
// returns the element with index nIndex
T& operator[](size_t nIndex) { return T(); }
};
template<class T>
class UnchunkHolder
{
size_t m_nSize = 0;
public:
UnchunkHolder(size_t nSize) : m_nSize(nSize) {}
// returns the address of the chunk nChunkIndex
T& operator[](size_t nIndex) { return T(); }
};
然后,我们定义帮助程序函数来创建一个或多个类:
template <typename T, size_t SIZE> ChunkHolder<T, SIZE>
Holder(size_t nSize) { return {nSize}; }
template <typename T> UnchunkHolder<T>
Holder(size_t nSize) { return {nSize}; }
最后,我们可以这样使用它:
auto x = Holder<int, 200u>(5000u);
auto y = Holder<int>(500u);
x
是块功能的Holder
1,y
缺少该功能,并且无法编译GetChunk
调用,只是因为底层类型缺少该函数。
在此处查看现场演示。
- 好吧,它不是,是一个
ChunkHolder
,你可以创建一个具有通用实现(operator[]
,...
)的基类或使用不同的类;这取决于你的实现需求。
基本上没有标准方法来知道编译器是否添加了默认值,或者用户是否实际键入了默认值。当你的代码可以开始区分时,价值已经存在。
特定的编译器可以提供这样的钩子(例如__is_defaulted(nChunkSize)
),仔细检查编译器文档可能会有所帮助,但常见的编译器似乎不提供这样的功能。
我不确定用例的确切性质;但"通常"的选择是使用部分模板专业化来区分实现,而不是真正关心nChunkSize
的价值来自哪里,而是价值是什么。
#include <iostream>
using namespace std;
template <typename T, size_t nChunkSize = 1000>
struct A {
A() { cout << "A()" << endl; }
};
template <typename T>
struct A<T, 1000> {
A() { cout << "A() special" << endl; }
};
int main() {
A<int, 100> a1; // prints A()
A<int> a2; // prints A() special
return 0;
}
演示示例。可以根据需要将更多常见详细信息移动到特征类或基类。
但是,以上并不能完全实现您想要的。使您更接近目标帖子的替代方法包括使用可用于区分用户提供的值或使用默认值的"特殊"值。理想情况下,该值是用户不太可能使用它; 在这种情况下,我想到了0
。它仍然不能保证用户会使用0
,但在"合理"的客户端代码中不太可能有0
块大小。
template<class T, size_t nChunkSize = 0>
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize == 0 ? 1000 : nChunkSize;
// ...
然后可以使用static_assert
根据nChunkSize
的值允许是否编译GetChunk
- 这是有效的,因为nChunkSize
在编译时是已知的。
T* GetChunk(size_t nChunkIndex)
{
static_assert(nChunkSize != 0, "Method call invalid without client chunk size");
// ...
缺点是GetChunk
在开发过程中仍然"可见",但如果调用它,编译将失败。
你可以得到的最接近的已经提到过,但在这里重复进行比较;是将类的实现推迟到某个BaseHolder
,然后将其与部分模板专用化相结合,以确定客户端代码是否使用了块大小(nChunkSize
的值)或不。
template<typename T, size_t nChunkSize /*=1000*/>
// default not provided here, it is not needed
class BaseHolder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
// ...
};
template<typename T, size_t... ARGS>
class Holder : public Base<T, 1000>
{
// "default" value for nChunkSize required
// When sizeof...(ARGS) = 1, the specialisation is used
// when 0, the client has not provided the default
// when 2 or more, it is invalid usage
static_assert(sizeof...(ARGS) == 0, "Only 1 size allowed in the client code");
// ...
};
template<typename T, size_t nChunkSize>
class Holder<T, nChunkSize> : public Base<T, nChunkSize>
{
// non-default chunk size used (could still be 1000)
// includes the implementation of GetChunk
public:
T* GetChunk(size_t nChunkIndex)
{
// ...
}
};
这种方法的缺点是可以提供多个size_t
参数,这可以在编译时用static_assert
控制;代码的文档也应该清楚地说明这一点。
- 如果输入类型与目标类型不同,"cin"变量是否重置为某个默认值?
- 具有默认值的单个参数构造函数是否与默认构造函数相同?
- const变量是否可以在具有默认值的参数中赋值(作为可选参数)
- 是否有C++准则将第一个枚举设置为"safe"默认值?
- boost::p rogram_options:是否可以将一个选项设置为另一个选项的默认值
- 是否可以在运行时更改C++类的默认值?
- 是否可以将`constexpr`模板变量作为正式模板参数的默认值
- 检查信号处理程序是否不是默认值
- 是否可以设计一个包含模板参数默认值的类
- 是否可以提示用户输入并提供默认值
- 如何确定模板参数是否为默认值
- -std=c++11 编译器标志是否会在某个时候成为默认值
- 如果没有定义Move语义(Move构造函数和Move赋值操作符),编译器是否默认优化
- c++默认初始化是否将数组元素设置为默认值?
- 联合的默认值是否总是为零?
- C++和提升 XML 存档(序列化):是否可以指定元素的标记名称?(默认值:"item" )
- map检查map是否被分配了非默认值
- 如何检查注册表中是否未设置(默认)值
- 将函数指针上的参数与默认值参数一起丢弃是否有效
- 函数的未命名参数是否可以具有默认值