Как специализировать шаблонный класс для классификации типов данных? - PullRequest
1 голос
/ 10 февраля 2010

Мы используем boost - поэтому с этой библиотекой все будет в порядке.

Но мне никогда не удавалось создать набор шаблонов, которые дают вам правильную специализацию для целого класса типов данных, в отличие от специализации для одного типа данных (что я знаю, как это сделать) .

Позвольте мне привести пример, чтобы попытаться донести это до земли. Я хочу иметь набор классов, которые можно использовать как:

Initialized<T> t;

Где T - простой базовый тип, PODS или массив. Это не может быть класс, поскольку ожидается, что у класса будет собственный конструктор, и перезапись его необработанной памяти - ужасная идея.

Инициализированный должен в основном memset (& t, 0, sizeof (t)); Это облегчает гарантию того, что исполняемый код не отличается от кода отладки при работе с устаревшими структурами.

Инициализируется, когда SDT = простой тип данных, должен просто создать структуру, которая обертывает базовый SDT и использует компиляторы t () для генерации конструктора, определенного компилятором по умолчанию для этого типа (он также может составить memset, хотя кажется более элегантным просто привести к t ().

Вот удар, используя Initialized <> для POD и Initialized <> для SDT:

// 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; }
};

А для СДЦ:

// 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;
};

Я специализировал Initialized для T *, чтобы обеспечить естественное поведение указателя. И у меня есть InitializedArray <> для массивов, который принимает как тип элемента, так и размер массива в качестве аргументов шаблона. Но опять же, я должен использовать имя шаблона, чтобы различать - я не умею использовать MPL достаточно хорошо, чтобы предоставить единый шаблон, который приводит к правильной специализации во время компиляции из одного имени (Initialized <>, в идеале).

Хотелось бы также иметь возможность предоставить перегруженный 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 '=': не найден оператор, который принимает правый операнд типа '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 ++ позволял мне наследовать от базовых типов, я мог бы просто использовать технику наследования, и все было бы хорошо.

Или, если бы у меня был какой-то способ сказать компилятору экстраполировать все методы от parent к child, чтобы все допустимое для parent было допустимым для child, я был бы в порядке.

Или если бы я мог использовать MPL или type-traits для typedef вместо того, чтобы наследовать то, что мне нужно, тогда не было бы дочернего класса и проблемы с распространением.

Идеи?! ...

Ответы [ 3 ]

2 голосов
/ 10 февраля 2010

Инициализирован должен в основном 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 никогда не будет сообщить, что класс или структура является 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);
}

Однако, Initialized, вероятно, никогда не будет так хорош, как реальная вещь. В тестовом коде показан один недостаток: он может помешать выводу типа шаблона. Кроме того, для массивов у вас будет выбор: если вы хотите инициализировать его нулем с помощью конструктора, то у вас не может быть инициализации массива не по умолчанию.

Если используется то, что вы собираетесь отследить все неинициализированные переменные и включить их в Initialized, я не совсем уверен, почему вы не просто инициализируете их самостоятельно.

Кроме того, для отслеживания неинициализированных переменных могут быть полезны предупреждения компилятора.

0 голосов
/ 11 февраля 2010

Так как я смог использовать ответы 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<>
//  /1750575/kak-spetsializirovat-shablonnyi-klass-dlya-klassifikatsii-tipov-dannyh
//
//  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();
}
0 голосов
/ 10 февраля 2010

Я знаю, что это не отвечает на ваш вопрос, но я думал, что структуры POD всегда были инициализированы нулями.

...