如何确定模板参数是否默认?

时间:2016-02-23 12:17:42

标签: c++ templates c++11 c++14

我有这个简化的类(省略了许多细节):

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

4 个答案:

答案 0 :(得分:3)

您可以使用具有两个派生类的公共基类:一个专门用于提供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;
};

Nondefaulted(有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;
    }
};

Demo

答案 1 :(得分:2)

如果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,但可能您并没有计划将值传递给高public class MyEntity { private readonly IValidatorFactory _validatorFactory; public MyEntity(IValidatorFactory validatorFactory) { _validatorFactory = validatorFactory; } //Entity Properties Removed For Clarity public void Validate() { if(_validatorFactory == null) { throw new ArgumentNullException("Validator Factory was null, cannot validate this item"); } var validator = _validatorFactory.GetValidator(this.ItemTypeId); valid = validator.Validate(); } }

Live Demo

答案 2 :(得分:1)

我建议使用两个不同的类:如果他们预计会有不同的实现,为什么还要坚持一个单一的定义呢?

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 ,具有chunk功能,y缺少该功能,无法编译GetChunk调用,只是因为底层类型缺乏该功能。

查看现场演示 here

  1. 嗯,它不是ChunkHolder,您可以使用通用实现(operator[]...)创建基类或使用不同的类;这取决于您的实施需求。

答案 3 :(得分:0)

基本上没有标准方法可以知道编译器是否添加了默认值,或者用户是否物理输入了它。当您的代码开始区分时,该值已经存在。

特定的编译器可以提供这样的钩子(例如__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;
}

Demo sample。可以根据需要将更多常见细节移动到特征类或基类。

上面并没有达到你想要的效果。让您更接近目标职位的替代方案包括使用“特殊”值,该值可用于区分用户提供的值,或使用默认值。理想情况下,价值就是用户不太可能使用它;在这种情况下,我会想到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来允许根据GetChunk的值来编译nChunkSize - 这是有效的,因为在编译时已知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控制;代码的文档也应该清楚。