Шаблоны и ковариантность типов
От: 24  
Дата: 02.01.10 00:05
Оценка:
Как-то можно обойти ошибку, которую выдаёт компилятор, в следующем коде? Т.е. заставить его воспринимать унаследованный тип как базовый. Компилилось в 2008-й вижуал студии.

template <typename T>
class Templated {
    ...
};

class A {
    ...
};

class B : public A {
    ...
};


int main() {
    // тут ошибка C2440, что нельзя преобразовать один тип к другому
    Templated<A*>* obj = new Templated<B*>();
}
Re: Шаблоны и ковариантность типов
От: jazzer Россия Skype: enerjazzer
Дата: 02.01.10 00:22
Оценка: 2 (1)
Здравствуйте, 24, Вы писали:

24>Как-то можно обойти ошибку, которую выдаёт компилятор, в следующем коде? Т.е. заставить его воспринимать унаследованный тип как базовый. Компилилось в 2008-й вижуал студии.


Придется написать руками соответствующий шаблонный конструктор, который будет содержать проверку типа, примерно так (не компилировал, пишу прямо в браузере):
#include  <boost/type_traits/is_base_of.hpp>
#include  <boost/static_assert.hpp>

template <typename T>
class Templated {
    template <class U> Templated( const Templated<U>& rhs )
    {
      BOOST_STATIC_ASSERT( boost::is_base_of<T, U>::value );
      ...
    }
};

Эту проверку можно также запихнуть в enable_if, либо в BOOST_MPL_ASSERT_MESSAGE и сделать читабельный error.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re: Шаблоны и ковариантность типов
От: Кодт Россия  
Дата: 04.01.10 20:17
Оценка:
Здравствуйте, 24, Вы писали:

24>Как-то можно обойти ошибку, которую выдаёт компилятор, в следующем коде? Т.е. заставить его воспринимать унаследованный тип как базовый.


Для указателей — Templated<B>* --> Templated<A>* — нужно, чтобы Templated<B> был унаследован от Templated<A>.
Это можно сделать, если иерархия прообразов (A,B) известна.
Либо явно, либо ввести метафункцию baseclass, определённую над прообразами.
template<class T> struct deftype { typedef T type; };

template<class T> struct baseclass : deftype<void> {};

struct A { ... };

struct B : A { ... };
template<> struct baseclass<B> : deftype<A> {};

struct C : B { ... };
template<> struct baseclass<C> : deftype<B> {};

.....

template<class T> struct Templated;
template<> struct Templated<void> {};
template<class T> struct Templated
  : Templated<typename baseclass<T>::type>
  { ... };
  // нужно ещё позаботиться, чтобы объекты не разраслись из-за наследования один от другого

Templated<A>* pta = new Templated<C>();


Для объектов же, Templated<B> --> Templated<A> — достаточно определить пользовательские приведения типов: конструкторами или операторами, по вкусу.
Пример — boost::shared_ptr, где воспроизводится такая же ковариантность, как у голых указателей.
Перекуём баги на фичи!
Re: Шаблоны и ковариантность типов
От: ollv СССР https://youtu.be/DQDoYs6wHoo
Дата: 05.01.10 00:24
Оценка:
Здравствуйте, 24, Вы писали:

24>Как-то можно обойти ошибку, которую выдаёт компилятор, в следующем коде? Т.е. заставить его воспринимать унаследованный тип как базовый. Компилилось в 2008-й вижуал студии.


24>
24>template <typename T>
24>class Templated {
24>    ...
24>};

24>class A {
24>    ...
24>};

24>class B : public A {
24>    ...
24>};



struct _Empty;

template <typename _Ty>
   struct _Templ_parser   {  struct _Empty result; };

template <typename _Ty_p, template <class> class _Ty_templ>
   struct _Templ_parser<_Ty_templ<_Ty_p> >
{
   typedef _Ty_p result;
};

template <typename _Ty_dst, typename _Ty_src>
   struct _Converter
{
   typedef _Empty result;
};

template <typename _Ty_dst, typename _Ty_src, template <class> class _Ty_templ>
   struct _Converter<_Ty_dst, _Ty_templ<_Ty_src> >
{
   typedef _Ty_templ<_Ty_dst> result;
};
   
template <typename _Dst, typename _Src>  
  typename _Converter<_Dst, _Src>::result* caster(_Src *ptr)
{
    typedef _Templ_parser<_Src> _Par_type;
    return boost::is_base_of<_Dst, _Par_type>::value ? reinterpret_cast<typename _Converter<_Dst, _Src>::result*>( ptr):0;
}

int main() {
    // тут ошибка C2440, что нельзя преобразовать один тип к другому
    Templated<A*>* obj = caster<A*>(new Templated<B*>());
}

24>


А вообще там неплохо еще всякие is_ptr преобразования (и не базироваться на пттрах) и большее обобщение кейсов и чуть подшаманить конвертер добавить конструктор и оператор преобразования к дст, в общем более изяшно реализовать идею

П,С, не компилил наверное есть ошибки
Compiler can be as trained AI but can't compose music.
Antheil piano jazz sonata. Я болен ПГМ.
Re: Шаблоны и ковариантность типов
От: naf2000  
Дата: 09.08.11 12:14
Оценка:
аналогично

class A {}
class B : public A {}
...
vector<B*> vb;
vector<A*> va=vb;
но... я не хотел бы выделять память бы под новую коллекцию, я хочу иметь обертку над vector<B*>, но с интерфейсом vector<A*>
реально?
Re[2]: Шаблоны и ковариантность типов
От: jazzer Россия Skype: enerjazzer
Дата: 10.08.11 00:04
Оценка:
Здравствуйте, naf2000, Вы писали:

N>аналогично


N>
N>class A {}
N>class B : public A {}
N>...
N>vector<B*> vb;
N>vector<A*> va=vb;
N>
но... я не хотел бы выделять память бы под новую коллекцию, я хочу иметь обертку над vector<B*>, но с интерфейсом vector<A*>

N>реально?


va.push_back( new A ); // ну а чё, vector<A*> же
...
B* pb = vb.back(); // ой?
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[3]: Шаблоны и ковариантность типов
От: se_sss  
Дата: 10.08.11 16:14
Оценка:
J>
J>va.push_back( new A ); // ну а чё, vector<A*> же
J>...
J>B* pb = vb.back(); // ой?
J>


Не ой, когда куда-то передаёшь кому-то вектор vector<A*> в режиме только на чтение.
Т.е. удобно в одном месте вектор подготовить, пользуясь более безопасной типизацией, зная что это B.
(В другом месте, например, уже С).
А пользователю передаём массив для чтения. И он уже пользуется только ограниченным интерфейсом A.

Но, как я понимаю, нормально реализовать это в C++ нельзя.
Re[4]: Шаблоны и ковариантность типов
От: jazzer Россия Skype: enerjazzer
Дата: 10.08.11 18:30
Оценка:
Здравствуйте, se_sss, Вы писали:


J>>
J>>va.push_back( new A ); // ну а чё, vector<A*> же
J>>...
J>>B* pb = vb.back(); // ой?
J>>


_>Не ой, когда куда-то передаёшь кому-то вектор vector<A*> в режиме только на чтение.

_>Т.е. удобно в одном месте вектор подготовить, пользуясь более безопасной типизацией, зная что это B.
_>(В другом месте, например, уже С).
_>А пользователю передаём массив для чтения. И он уже пользуется только ограниченным интерфейсом A.

В случае вектора проще всего передавать пару указателей begin/end (через Boost.Range, например) — указатели можно кастить к чему угодно.
Еще вариант обёртки указателей — boost::multi_array_ref.
Еще вариант обёртки итераторов вообще: boost::transform_iterator.

_>Но, как я понимаю, нормально реализовать это в C++ нельзя.


Можно, но ты же понимаешь, это уже не совсем std::vector будет, раз у него интерфейс до read-only ограничивается. Так что тут специальный тип-адаптер нужен, как-то так (пишу в браузере, просто идею продемонстрировать):
template<class Base, class Container>
struct ptr_vector_adapter
{
  const Container& c_;
  BOOST_STATIC_ASSERT(( boost::is_pointer<typename Container::value_type>::value ));
  BOOST_STATIC_ASSERT(( boost::is_base_of< Base, typename boost::remove_pointer<typename Container::value_type>::type >::value ));

  typedef Base* value_type;
  typedef const value_type& const_reference;
  typedef const value_type* const_iterator;
  typedef typename typename Container::size_type size_type;

  const_reference operator[](size_type i) const { return c_[i]; }
  const_reference at(size_type i) const { return c_.at(i); }

  const_reference front() const { return c_.front(); }
  const_reference back()  const { return c_.back(); }

  size_type size() const { return c_.size(); }
  size_type max_size() const { return c_.max_size(); }
  size_type capacity() const { return c_.capacity(); }

  const_iterator begin() const { return c_.begin(); }
  const_iterator end()   const { return c_.end(); }

  // и т.д.
};


По-хорошему надо свой тип кастящего итератора делать тоже, потому что, например, в STLPort в дебажной версии итераторы у вектора — это не указатели, а объекты, проверяющие выход за границы и т.п., и код выше для них не скомпилится.

В любом случае, в код, принимающий строго std::vector<A>, ничто из вышеупомянутого не залезет.
Надо либо писать на итераторах, тогда все будет работать с любым контейнером (но код итерации будет шаблонным), либо на паре указателей (random-access range), если не хочется разводить шаблоны.

С++ тут ни при чем, потому что можно легко представить себе случаи, когда автоматическая конверсия будет неправильной. Например, уже представление std::vector<B> в виде std::vector<A>, очевидно, даст неправильный результат, хотя для указателей оно работало бы.
jazzer (Skype: enerjazzer) Ночная тема для RSDN
Автор: jazzer
Дата: 26.11.09

You will always get what you always got
  If you always do  what you always did
Re[5]: Шаблоны и ковариантность типов
От: se_sss  
Дата: 10.08.11 20:48
Оценка:
Вариант с range, пожалуй, самый удобный.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.