【问题标题】:How do I specialize a templated class for data type classification?如何为数据类型分类专门化模板类?
【发布时间】:2010-02-10 15:54:50
【问题描述】:

我们使用 boost - 所以使用那个库应该没问题。

但是我从来没有设法创建一组模板来为您提供针对整个数据类型的正确专业化,而不是专门针对单个数据类型(我知道该怎么做) .

让我举个例子来试着把它变成现实。我想要一组可以用作的类:

Initialized<T> t;

其中 T 可以是简单的基本类型、PODS 或数组。它不能是一个类,因为一个类应该有自己的构造函数,而覆盖它的原始内存是一个糟糕的主意。

初始化应该基本上是memset(&t, 0, sizeof(t));在处理遗留结构时,它可以更轻松地确保运行时代码与调试代码没有区别。

在 SDT = 简单数据类型的情况下初始化,应该简单地创建一个包装底层 SDT 的结构,并使用编译器 t() 为该类型生成编译器定义的默认构造函数(它也可能相当于一个 memset,尽管它简单地导致 t() 似乎更优雅。

这是一个尝试,对 POD 使用 Initialized,对 SDT 使用 Initialized:

// zeroed out PODS (not array)
// usage:  Initialized<RECT> r;
template <typename T>
struct Initialized : public T
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    // publish our underlying data type
    typedef T DataType;

    // default (initialized) ctor
    Initialized() { Reset(); }

    // reset
    void Reset() { Zero((T&)(*this)); }

    // auto-conversion ctor
    template <typename OtherType> Initialized(const OtherType & t) : T(t) { }

    // auto-conversion assignment
    template <typename OtherType> Initialized<DataType> & operator = (const OtherType & t) { *this = t; }
};

对于 SDT:

// Initialised for simple data types - results in compiler generated default ctor
template <typename T>
struct Initialised
{
    // default valued construction
    Initialised() : m_value() { }

    // implicit valued construction (auto-conversion)
    template <typename U> Initialised(const U & rhs) : m_value(rhs) { }

    // assignment
    template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }

    // implicit conversion to the underlying type
    operator T & () { return m_value; }
    operator const T & () const { return m_value; }

    // the data
    T   m_value;
};

我专门为 T* 进行了初始化,以提供自然的指针行为。而且我有一个用于数组的 InitializedArray,它将元素类型和数组大小作为模板参数。但同样,我必须使用模板名称来区分 - 我对 MPL 的理解不够好,无法提供一个模板,该模板在编译时导致正确的专业化,所有这些都来自一个名称(理想情况下是初始化)。

我也希望能够提供一个重载的 Initialized ,这样对于非标量值,用户可以定义默认初始化值(或 memset 值)

我很抱歉问了一些可能需要花点力气才能回答的问题。这似乎是我自己阅读 MPL 时无法克服的障碍,但也许在您的帮助下,我可能能够确定此功能!


根据下面本叔叔的回答,我尝试了以下方法:

// containment implementation
template <typename T, bool bIsInheritable = false>
struct InitializedImpl
{
    // publish our underlying data type
    typedef T DataType;

    // auto-zero construction
    InitializedImpl() : m_value() { }

    // auto-conversion constructor
    template <typename U> InitializedImpl(const U & rhs) : m_value(rhs) { }

    // auto-conversion assignment
    template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }

    // implicit conversion to the underlying type
    operator T & () { return m_value; }
    operator const T & () const { return m_value; }

    // the data
    T   m_value;
};

// inheritance implementation
template <typename T>
struct InitializedImpl<T,true> : public T
{
    // publish our underlying data type
    typedef T DataType;

    // auto-zero ctor
    InitializedImpl() : T() { }

    // auto-conversion ctor
    template <typename OtherType> InitializedImpl(const OtherType & t) : T(t) { }

    // auto-conversion assignment
    template <typename OtherType> InitializedImpl<DataType> & operator = (const OtherType & t) { *this = t; }
};

// attempt to use type-traits to select the correct implementation for T
template <typename T>
struct Initialized : public InitializedImpl<T, boost::is_class<T>::value>
{
    // publish our underlying data type
    typedef T DataType;
};

然后尝试了几个使用测试。

int main()
{
    Initialized<int> i;
    ASSERT(i == 0);
    i = 9;  // <- ERROR
}

这会导致错误:*binary '=' : no operator found 采用'InitializedImpl ' 类型的右侧操作数(或没有可接受的转换)

而如果我直接实例化正确的基类型(而不是派生类型):

int main()
{
    InitializedImpl<int,false> i;
    ASSERT(i == 0);
    i = 9;  // <- OK
}

现在我可以将 i 用作任何旧的 int。这就是我想要的!

如果我尝试对结构做同样的事情,就会出现完全相同的问题:

int main()
{
    Initialized<RECT> r;
    ASSERT(r.left == 0);  // <- it does let me access r's members correctly! :)

    RECT r1;
    r = r1;  // <- ERROR

    InitializedImpl<RECT,true> r2;
    r2 = r1; // OK
}

所以,如您所见,我需要一些方法来告诉编译器将 Initialized 提升为像真正的 T 一样。

如果 C++ 让我从基本类型继承,我可以只使用继承技术,一切都会好起来的。

或者,如果我有办法告诉编译器将父级中的所有方法外推到子级,这样在父级上有效的任何东西对子级都有效,我会没事的。

或者如果我可以使用 MPL 或 type-traits 来 typedef 而不是继承我需要的东西,那么就不会有子类,也不会有传播问题。

想法?!...

【问题讨论】:

  • 时间太少,无法真正了解它:似乎部分专业化,可能与boost::enable_if 一起使用,应该可以满足您的需求。

标签: c++ templates boost-mpl


【解决方案1】:

初始化应该基本上 memset(&t, 0, sizeof(t));它使它 更容易确保运行时代码 与调试代码没有什么不同 处理遗留结构。

我认为您不需要 memset,因为您可以对 POD 进行零初始化,就像您可以显式调用非 POD 的默认构造函数一样。 (除非我弄错了)。

#include <cassert>

struct X {int a, b; };

template <typename T>
struct Initialized
{
    T t;

    // default (initialized) ctor
    Initialized(): t()  { }

};

template <typename T>
struct WithInheritance: public T
{
    // default (initialized) ctor
    WithInheritance(): T()  { }
};

int main()
{
    Initialized<X> a;
    assert(a.t.a == 0 && a.t.b == 0);

    //it would probably be more reasonable not to support arrays,
    //and use boost::array / std::tr1::array instead
    Initialized<int[2]> b;
    assert(b.t[0] == 0 && b.t[1] == 0);

    WithInheritance<X> c;
    assert(c.a == 0 && c.b == 0);
}

在确定类型的 pod-ness 时,您还可以考虑 boost::is_pod 参考中的此注释:

没有一些(尚未指定的)帮助 从编译器来看, is_pod 永远不会 报告一个类或结构是 荚;如果可能的话,这总是安全的 次优。目前仅(2005 年 5 月) MWCW 9 和 Visual C++ 8 具有 必要的编译器内在函数。

(我认为 boost::type_traits 正在将其纳入 C++0x 中的标准库,在这种情况下,期望 is_pod 确实有效是合理的。)


但是如果你想基于一个条件进行特化,你可以引入一个 bool 参数。例如这样的:

#include <limits>
#include <cstdio>

template <class T, bool b>
struct SignedUnsignedAux
{
    void foo() const { puts("unsigned"); }
};

template <class T>
struct SignedUnsignedAux<T, true>
{
    void foo() const { puts("signed"); }
};

//using a more reliable condition for an example
template <class T>
struct SignedUnsigned: SignedUnsignedAux<T, std::numeric_limits<T>::is_signed > {};

int main()
{
    SignedUnsigned<int> i;
    SignedUnsigned<unsigned char> uc;
    i.foo();
    uc.foo();
}

这也有点像您想象的那样工作(至少与 MinGW 4.4 和 VC++ 2005 一起编译 - 后者也很好地产生了一个 警告 数组将被零初始化!: ))。

这使用了一个默认的布尔参数,你可能永远不应该自己指定。

#include <boost/type_traits.hpp>
#include <iostream>

template <class T, bool B = boost::is_scalar<T>::value>
struct Initialized
{
    T value;
    Initialized(const T& value = T()): value(value) {}
    operator T& () { return value; }
    operator const T& () const { return value; }
};

template <class T>
struct Initialized<T, false>: public T
{
    Initialized(const T& value = T()): T(value) {}
};

template <class T, size_t N>
struct Initialized<T[N], false>
{
    T array[N];
    Initialized(): array() {}
    operator T* () { return array; }
    operator const T* () const { return array; }
};

//some code to exercise it

struct X
{
    void foo() const { std::cout << "X::foo()" << '\n'; }
};

void print_array(const int* p, size_t size)
{
    for (size_t i = 0; i != size; ++i) {
        std::cout << p[i] <<  ' ';
    }
    std::cout << '\n';
}

template <class T>
void add_one(T* p, size_t size)
{
    for (size_t i = 0; i != size; ++i) {
        p[i] += T(1);
    }
}

int main()
{
    Initialized<int> a, b = 10;
    a = b + 20;
    std::cout << a << '\n';
    Initialized<X> x;
    x.foo();
    Initialized<int[10]> arr /*= {1, 2, 3, 4, 5}*/; //but array initializer will be unavailable
    arr[6] = 42;
    add_one<int>(arr, 10);  //but template type deduction fails
    print_array(arr, 10);
}

但是,Initialized 可能永远不会像真实的那样好。测试代码中显示了一个缺点:它会干扰模板类型推导。此外,对于数组,您可以选择:如果您想使用构造函数对其进行零初始化,那么您不能进行非默认数组初始化。

如果您要跟踪所有未初始化的变量并将它们包装到 Initialized 中,我不太确定您为什么不自己初始化它们。

此外,对于跟踪未初始化的变量,编译器警告可能会有所帮助。

【讨论】:

  • 优秀。听起来我可以只使用 Initialized 而无需担心它是数组、结构还是简单类型,而不是指针,如果我只是为上述所有内容提供必要的运算符。对于自然成员访问语义,我会使用 with-inheritance 模型,但我还想支持不能继承的简单类型。
  • 我剩下的主要问题是,我如何将整数值(可能是枚举)用于专业化方面,而不是布尔值。因此,如果我可以 Initialized,那么我可能会很成功。例如。我不想提供数组索引运算符,除非类型是数组。同样,这是针对遗留代码,作为一个插入式包装器,而不是针对将使用向量或其他东西的更新、更好的代码。
  • 你可以使用除 bool 之外的任何整数类型。但是您可能只需要一种用于内置类型的特化,另一种用于可以继承的任何东西。
  • 我在尝试实施您的方法时遇到的麻烦是我不能像使用 T 一样使用 Initialized。初始化 i;我 = 5; 二进制 '=' : 未找到采用 'int' 类型的右侧操作数的运算符。我的 Initialised 模板适用于这种用法。但是当将其抽象为 InitializedImpl 或 InitializedImpl 是超类型时,编译器似乎迷失了如何处理 Initialized : InitailizedImpl::value> 除非我跳过并直接实例化 InitializedImpl i;
  • 就像我需要编译器允许我根据使用情况生成正确的类,而不是根据使用情况生成正确基类的子类!
【解决方案2】:

我知道它不能回答您的问题,但我认为 POD 结构始终是零初始化的。

【讨论】:

  • POD 的默认初始化将转换为零初始化,但并非在所有情况下都会发生。
  • 翻译为 - 没有意识到这一点。但这仍然意味着它必须是一个成员变量,以便编译器生成该默认(零)构造函数。如果我有一个 RECT,并 delcare 一个 RECT r;在函数体中,它没有被初始化。因此,使用 Initialized r; 可以很方便并有助于使我的代码更加健壮。这对我来说是零。
【解决方案3】:

由于我能够使用 UncleBen 的答案创建一个全面的解决方案(与我认为在 C++ 中的这一点一样好),我想在下面分享它:

请随意使用它,但我不保证它是否值得用于任何用途,等等等等,作为一个成年人并为你自己该死的行为负责,等等,等等:

//////////////////////////////////////////////////////////////
// Raw Memory Initialization Helpers
//
//  Provides:
//      Zero(x) where x is any type, and is completely overwritten by null bytes (0).
//      Initialized<T> x; where T is any legacy type, and it is completely null'd before use.
//
// History:
//
//  User UncleBen of stackoverflow.com and I tried to come up with 
//  an improved, integrated approach to Initialized<>
//  http://stackoverflow.com/questions/2238197/how-do-i-specialize-a-templated-class-for-data-type-classification
//
//  In the end, there are simply some limitations to using this
//  approach, which makes it... limited.
//
//  For the time being, I have integrated them as best I can
//  However, it is best to simply use this feature
//  for legacy structs and not much else.
//
//  So I recommend stacked based usage for legacy structs in particular:
//      Initialized<BITMAP> bm;
//
//  And perhaps some very limited use legacy arrays:
//      Initialized<TCHAR[MAX_PATH]> filename;
//
//  But I would discourage their use for member variables:
//      Initialized<size_t> m_cbLength;
//  ...as this can defeat template type deduction for such types 
//  (its not a size_t, but an Initialized<size_t> - different types!)
//
//////////////////////////////////////////////////////////////

#pragma once

// boost
#include <boost/static_assert.hpp>
#include <boost/type_traits.hpp>

// zero the memory space of a given PODS or native array
template <typename T>
void Zero(T & object, int zero_value = 0)
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    // make zeroing out a raw pointer illegal
    BOOST_STATIC_ASSERT(!(boost::is_pointer<T>::value));

    ::memset(&object, zero_value, sizeof(object));
}

// version for simple arrays
template <typename T, size_t N>
void Zero(T (&object)[N], int zero_value = 0)
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    ::memset(&object, zero_value, sizeof(object));
}

// version for dynamically allocated memory
template <typename T>
void Zero(T * object, size_t size, int zero_value = 0)
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    ::memset(object, zero_value, size);
}

//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////


//////////////////////////////////////////////////////////////////////////
// Initialized for non-inheritable types
// usage: Initialized<int> i;
template <typename T, bool SCALAR = boost::is_scalar<T>::value>
struct Initialized
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_scalar<T>::value));

    // the data
    T   m_value;

    // default valued construction
    Initialized() : m_value() { }

    // implicit valued construction (auto-conversion)
    template <typename U> Initialized(const U & rhs) : m_value(rhs) { }

    // assignment
    template <typename U> T & operator = (const U & rhs) { if ((void*)&m_value != (void*)&rhs) m_value = rhs; return *this; }

    // implicit conversion to the underlying type
    operator T & () { return m_value; }
    operator const T & () const { return m_value; }

    // zero method for this type
    void _zero() { m_value = T(); }
};

//////////////////////////////////////////////////////////////////////////
// Initialized for inheritable types (e.g. structs)
// usage:  Initialized<RECT> r;
template <typename T>
struct Initialized<T, false> : public T
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    // default ctor
    Initialized() : T() {  }

    // auto-conversion ctor
    template <typename OtherType> Initialized(const OtherType & value) : T(value) { }

    // auto-conversion assignment
    template <typename OtherType> Initialized & operator = (const OtherType & value) { *this = value; }

    // zero method for this type
    void _zero() { Zero((T&)(*this)); }
};

//////////////////////////////////////////////////////////////////////////
// Initialized arrays of simple types
// usage: Initialized<char, MAXFILENAME> szFilename;
template <typename T, size_t N>
struct Initialized<T[N],false>
{
    // ensure that we aren't trying to overwrite a non-trivial class
    BOOST_STATIC_ASSERT((boost::is_POD<T>::value));

    // internal data
    T m_array[N];

    // default ctor
    //Initialized() : m_array() { } // Generates a warning about new behavior.  Its okay, but might as well not produce a warning.
    Initialized() { Zero(m_array); }

    // array access
    operator T * () { return m_array; }
    operator const T * () const { return m_array; }

    // NOTE: All of the following techniques leads to ambiguity.
    //       Sadly, allowing the type to convert to ArrayType&, which IMO should
    //       make it fully "the same as it was without this wrapper" instead causes
    //       massive confusion for the compiler (it doesn't understand IA + offset, IA[offset], etc.)
    //       So in the end, the only thing that truly gives the most bang for the buck is T * conversion.
    //       This means that we cannot really use this for <char> very well, but that's a fairly small loss
    //       (there are lots of ways of handling character strings already)

    //  // automatic conversions
    //  operator ArrayType& () { return m_array; }
    //  operator const ArrayType& () const { return m_array; }
    // 
    //  T * operator + (long offset) { return m_array + offset; }
    //  const T * operator + (long offset) const { return m_array + offset; }
    // 
    //  T & operator [] (long offset) { return m_array[offset]; }
    //  const T & operator [] (long offset) const { return m_array[offset]; }

    // metadata
    size_t GetCapacity() const { return N; }

    // zero method for this type
    void _zero() { Zero(m_array); }
};

//////////////////////////////////////////////////////////////////////////
// Initialized for pointers to simple types
// usage: Initialized<char*> p;
// Please use a real smart pointer (such as std::auto_ptr or boost::shared_ptr)
//  instead of this template whenever possible.  This is really a stop-gap for legacy
//  code, not a comprehensive solution.
template <typename T>
struct Initialized<T*, true>
{
    // the pointer
    T * m_pointer;

    // default valued construction
    Initialized() : m_pointer(NULL) { }

    // valued construction (auto-conversion)
    template <typename U> Initialized(const U * rhs) : m_pointer(rhs) { }

    // assignment
    template <typename U> T * & operator = (U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }
    template <typename U> T * & operator = (const U * rhs) { if (m_pointer != rhs) m_pointer = rhs; return *this; }

    // implicit conversion to underlying type
    operator T * & () { return m_pointer; }
    operator const T * & () const { return m_pointer; }

    // pointer semantics
    const T * operator -> () const { return m_pointer; }
    T * operator -> () { return m_pointer; }
    const T & operator * () const { return *m_pointer; }
    T & operator * () { return *m_pointer; }

    // allow null assignment
private:
    class Dummy {};
public:
    // amazingly, this appears to work.  The compiler finds that Initialized<T*> p = NULL to match the following definition
    T * & operator = (Dummy * value) { m_pointer = NULL; ASSERT(value == NULL); return *this; }

    // zero method for this type
    void _zero() { m_pointer = NULL; }
};

//////////////////////////////////////////////////////////////////////////
// Uninitialized<T> requires that you explicitly initialize it when you delcare it (or in the owner object's ctor)
//  it has no default ctor - so you *must* supply an initial value.
template <typename T>
struct Uninitialized
{
    // valued initialization
    Uninitialized(T initial_value) : m_value(initial_value) { }

    // valued initialization from convertible types
    template <typename U> Uninitialized(const U & initial_value) : m_value(initial_value) { }

    // assignment
    template <typename U> T & operator = (const U & rhs) { if (&m_value != &rhs) m_value = rhs; return *this; }

    // implicit conversion to underlying type
    operator T & () { return m_value; }
    operator const T & () const { return m_value; }

    // the data
    T   m_value;
};

//////////////////////////////////////////////////////////////////////////
// Zero() overload for Initialized<>
//////////////////////////////////////////////////////////////////////////

// version for Initialized<T>
template <typename T, bool B>
void Zero(Initialized<T,B> & object)
{
    object._zero();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-05
    • 2015-06-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多