Как мне специализировать шаблонный класс для классификации типов данных?
Вопрос
Мы используем boost - так что с использованием этой библиотеки все должно быть в порядке.
Но мне так и не удалось разобраться в создании набора шаблонов, которые дают вам правильную специализацию для целого класса типов данных, в отличие от специализации для одного типа данных (что я знаю, как сделать).
Позвольте мне привести пример, чтобы попытаться воплотить это в жизнь.Я хочу иметь набор классов, которые можно использовать как:
Initialized<T> t;
Где T - либо простой базовый тип, либо модули, либо массив.Это не может быть класс, поскольку ожидается, что у класса будет свой собственный конструктор, и перезаписывать его необработанную память - ужасная идея.
Инициализироваться должно в основном memset(&t, 0, sizeof(t));Это упрощает обеспечение того, чтобы код времени выполнения не отличался от кода отладки при работе с устаревшими структурами.
Инициализированный где SDT = простой тип данных, должен просто создать структуру, которая обертывает базовый SDT и использует компиляторы t() для генерации определенного компилятором конструктора по умолчанию для этого типа (это также может быть равно memset , хотя кажется более элегантным просто приводить к t() .
Вот пример того, как это сделать, используя инициализированный<> для модулей и инициализирован<> для SDTS:
// 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; }
};
И для SDTS:
// 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, чтобы предоставить единый шаблон, который приводит к правильной специализации во время компиляции, используя все одно имя (инициализированное<>, в идеале).
Я также хотел бы иметь возможность предоставлять перегруженный инициализированный<typename T,="" T="" init_value=""> , чтобы для нескалярных значений пользователь мог определить значение инициализации по умолчанию (или значение 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
}
Это приводит к ошибке:*двоичный файл '=' :не найден оператор, который принимает правый операнд типа '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
}
Итак, как вы можете видеть, мне нужен какой-то способ сообщить компилятору, чтобы инициализированное значение действовало как true T.
Если бы C ++ позволял мне наследовать от базовых типов, я мог бы просто использовать технику наследования, и все было бы хорошо.
Или, если бы у меня был какой-то способ сказать компилятору экстраполировать все методы в parent на child, чтобы все, что допустимо для parent, было допустимо для child, я был бы в порядке.
Или, если бы я мог использовать MPL или type-traits для typedef вместо наследования того, что мне нужно, тогда не было бы дочернего класса и проблем с распространением.
Идеи?!...
Решение
Инициализироваться должно в принципе memset(&t, 0, sizeof(t));Это позволяет легче гарантировать, что код времени выполнения не отличается от кода отладки при работе с устаревшими структурами.
Я не думаю, что вам должен понадобиться memset, потому что вы можете инициализировать модули нулевой инициализацией точно так же, как вы можете явно вызвать конструктор по умолчанию для не-модулей.(если только я не сильно ошибаюсь).
#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);
}
В своем стремлении определить модульность типа вы также можете принять во внимание это примечание из boost::is_pod reference:
Без некоторой (пока неуказанной) помощи от компилятора is_pod никогда не будет сообщать, что класс или структура являются POD;это всегда безопасно, если возможно неоптимально.В настоящее время (май 2005) только 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);
}
Однако инициализированный, вероятно, никогда не будет так хорош, как реальный.Одно короткое замыкание показано в тестовом коде:это может помешать вычету типа шаблона.Кроме того, для массивов у вас будет выбор:если вы хотите инициализировать его нулем с помощью конструктора, то у вас не может быть инициализации массива не по умолчанию.
Если использование заключается в том, что вы собираетесь отследить все неинициализированные переменные и преобразовать их в инициализированные, я не совсем уверен, почему вы просто не инициализируете их сами.
Кроме того, для отслеживания неинициализированных переменных, возможно, предупреждения компилятора могут очень помочь.
Другие советы
Я знаю, что это не отвечает на ваш вопрос, но я думал, что структуры POD в любом случае всегда инициализируются нулем.
Поскольку я смог использовать ответы Дядюшки Бена для создания комплексного решения (настолько хорошего, насколько, я думаю, оно возможно на данный момент в 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();
}