Russian Qt Forum

Программирование => С/C++ => Тема начата: Akon от Январь 18, 2015, 16:29



Название: static_cast элементов в контейнере
Отправлено: Akon от Январь 18, 2015, 16:29
Для простоты рассмотрю только upcast. Производный тип есть базовый тип (при открытом наследовании):
Код:
class Base {};
class Derived : public Base {};

Derived d;
Base& b = d;  // OK  (это upcast)

Тем не менее, подобный сценарий не работает для контейнеров, поскольку конечные типы (инстанцированные контейнеры) не связаны отношением открытого наследования:
Код:
std::vector<Derived*> ds;
ds.push_back(new Derived);
ds.push_back(new Derived);

std::vector<Base*>& bs = ds;  // Error

Убрать ошибку можно лишь грубым методом - reinterpret_cast:
Код:
std::vector<Base*>& bs = reinterpret_cast<std::vector<Base*>&>(ds);
и это будет безопасно лишь тогда, когда Base есть "первая база" Derived (т.е. когда при static_cast<Base*>(derived) не происходит изменения значения указателя).

Собственно, мне хочется как-то так:
Код:
std::vector<Base*>& bs = container_items_static_cast<Base*>(ds);  // ОК
без синтаксического оверхеда.

Есть ли где-то что-то подобное (в бусте, может, - давно туда не заглядывал) с необходимыми проверками, с перегрузкой под константность?


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 19, 2015, 00:08
Мне кажется, что это какой-то хард кор.
На мой взгляд, необходимо ручками перетащить элементы из одного контейнера в другой. Ну или написать функцию, если хочется сделать это в одну строчку.
То, что вы пытаетесь сделать, выглядит как-то грубо и по сути вы преобразуете class Heaven в class Earth. То, что это как-то работает через reinterpret cast - заслуга std::vector<void*>.
Но это просто мое мнение.


Название: Re: static_cast элементов в контейнере
Отправлено: Akon от Январь 19, 2015, 10:38
Это как раз для того, чтыбы избежать "необходимо ручками перетащить элементы из одного контейнера в другой. Ну или написать функцию, если хочется сделать это в одну строчку.". Смысл в отсутствии ненужных вычислений - это же С/С++, а не Java или C# :)

Я прекрасно понимаю, что происходит "за кулисами". Осознанно пользуюсь этим подходом.


Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Январь 19, 2015, 10:46
Мне кажется, что это какой-то хард кор.
"Простое" решение - работать с vector <Base *>, но тогда выплывает масса приведений после взятия эл-та. А делать везде vector <Derived *> еще хуже, все кишки наружу. Я унаследовался от контейнера и сохранил в нем ID типа явно - чтобы отловить недопустимое приведение в runtime. Но все равно привожу


Название: Re: static_cast элементов в контейнере
Отправлено: ViTech от Январь 19, 2015, 13:13
Насколько я понял:
Код
C++ (Qt)
std::vector<Base*>& bs = reinterpret_cast<std::vector<Base*>&>(ds);
вполне себе нормальное решение, только хочется, чтобы были дополнительные проверки на допустимость такого преобразования.

За буст не скажу, но можно попробовать написать container_items_static_cast самому, взяв в качестве основного преобразования reinterpret_cast, а проверку типов выполнять с помощью type_traits (http://www.cplusplus.com/reference/type_traits/).


Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Январь 19, 2015, 13:32
За буст не скажу, но можно попробовать написать container_items_static_cast самому, взяв в качестве основного преобразования reinterpret_cast, а проверку типов выполнять с помощью type_traits (http://www.cplusplus.com/reference/type_traits/).
Не вижу как это сработает если контейнер пуст. А непустой - так проще банальное dynamic_cast


Название: Re: static_cast элементов в контейнере
Отправлено: Akon от Январь 19, 2015, 15:01
Разумеется, дополнительные проверки необходимы, а точнее, требуется только, чтобы "... Base есть "первая база" Derived". Когда-то (2-3 года назад) наспех я сделал общее решение (на шаблонах) проверки первой базы, но оно, если мне не изменяет память, работало в рантайме, и type_traits тогда мне не помог. Сейчас в фоне я повторно интересуюсь проблемой.


Название: Re: static_cast элементов в контейнере
Отправлено: _Bers от Январь 21, 2015, 08:38
Собственно, мне хочется как-то так:
Код:
std::vector<Base*>& bs = container_items_static_cast<Base*>(ds);  // ОК
без синтаксического оверхеда.

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


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

Нет ничего нормального в грубых хаках, которые превращают код в мину замедленного действия.

Подобного рода функционал (с проверками) может предоставить только сам шаблон вектора, и больше никто.

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

И нужно точно понимать его внутреннее устройство, требования и ограничения, что бы реализовать безопасный каст.

Любая реализация снаружи - только на свой страх и риск.


Название: Re: static_cast элементов в контейнере
Отправлено: Akon от Январь 21, 2015, 10:33
Способ-хак с соответствующей проверкой (см. выше) как-раз гарантирует инвариант по указателю. Я рассматриваю в контейнере только указатели на объекты (см. выше), и в этом случае существенно:
1. размер эл-та контейнера остается тем-же;
2. проверка проверяет, что при касте не происходит смещения this.


Название: Re: static_cast элементов в контейнере
Отправлено: ViTech от Январь 21, 2015, 13:58
Можно попробовать начать с таких конструкций (набросал на довольно скорую руку и не тестировал).

Определяем общий шаблон для преобразования контейнеров:
Код
C++ (Qt)
template <class _DestContainer>
struct container_cast
{
   template <class _SourceContainer>
   static _DestContainer strict(_SourceContainer source)
   {
       const bool can_cast = std::is_same<_SourceContainer, _DestContainer>::value;
       static_assert(can_cast, "Incomplete cast");
       return static_cast<_DestContainer>(source);
   }
};

Далее специализируем его для более конкретных контейнеров, например, вида container<_Type, _Alloc> (vector, list и т.п.).:
Код
C++ (Qt)
template < template<typename, typename> class _DestContainerTemplate
        , typename _DestItem, typename _DestAlloc>
struct container_cast<_DestContainerTemplate<_DestItem, _DestAlloc> &>
{
   typedef _DestContainerTemplate<_DestItem, _DestAlloc> DestContainer;
 
   template < template<typename, typename> class _SourceContainerTemplate
            , typename _SourceItem, typename _SourceAlloc>
   static DestContainer & strict(_SourceContainerTemplate<_SourceItem, _SourceAlloc> &source)
   {
       const bool is_compatible_containers =
           std::is_same< _SourceContainerTemplate<_SourceItem, _SourceAlloc>
                       , _DestContainerTemplate<_SourceItem, _SourceAlloc>>::value
        && std::is_same< _SourceContainerTemplate<_DestItem, _DestAlloc>
                       , _DestContainerTemplate<_DestItem, _DestAlloc>>::value;
       static_assert(is_compatible_containers, "Incompatible containers");
 
       typedef typename std::remove_pointer<_SourceItem>::type SourceClass;
       typedef typename std::remove_pointer<_DestItem>::type DestClass;
       const bool is_compatible_inheritance =
           std::is_base_of<DestClass, SourceClass>::value;
       static_assert(is_compatible_inheritance, "Incompatible inheritance");
 
       return reinterpret_cast<DestContainer &>(source);
   }
};
Здесь проверки нужно изменить под свои требования.

Немного синтаксического сахара, чтобы типы контейнеров каждый раз не писать. И для уверенности, что преобразуются контейнеры одного типа:
Код
C++ (Qt)
template <class _DestItem>
struct container_items_cast
{
   template < template<typename, typename> class _ContainerTemplate
            , typename _SourceItem, template<typename> class _Allocator>
   static _ContainerTemplate<_DestItem, _Allocator<_DestItem>> &
   strict(_ContainerTemplate<_SourceItem, _Allocator<_SourceItem>> &source)
   {
       typedef _ContainerTemplate<_DestItem, _Allocator<_DestItem>> DestContainer;
 
       return container_cast<DestContainer &>::strict(source);
   }
};

Примеры использования:
Код
C++ (Qt)
class Base {};
class Derived : public Base {};
class Other {};
 
std::vector<Derived*> derived_vector;
derived_vector.push_back(new Derived);
derived_vector.push_back(new Derived);
 
std::list<Derived*> derived_list;
derived_list.push_back(new Derived);
derived_list.push_back(new Derived);
 
std::vector<Base*>& base_vector = container_cast<std::vector<Base*>&>::strict(derived_vector);
std::list<Base*>& base_list = container_cast<std::list<Base*>&>::strict(derived_list);
 
// Error: Incompatible containers
//std::list<Base*>& base_list_vector = container_cast<std::list<Base*>&>::strict(derived_vector);
// Error: Incompatible inheritance
//std::vector<Other*>& other_vector = container_cast<std::vector<Other*>&>::strict(derived_vector);
 
std::vector<Base*>& base_vector_by_items = container_items_cast<Base*>::strict(derived_vector);
std::list<Base*>& base_list_by_items = container_items_cast<Base*>::strict(derived_list);
 

Нет ничего нормального в грубых хаках, которые превращают код в мину замедленного действия.
...
И нужно точно понимать его внутреннее устройство, требования и ограничения, что бы реализовать безопасный каст.
Любая реализация снаружи - только на свой страх и риск.
В целом согласен, с хаками нужно быть осторожными. Но имеет ли право на существование преобразование
Код
C++ (Qt)
std::vector<Base*>& bs = static_cast<std::vector<Base*>&>(ds);
если есть гарантии,  что для всех элементов выполняется условие static_cast<Base*>(derived)? И как относиться к контейнеру, который указатели на базовые и производные объекты хранит по разному?

В любом случае, гарантии должны подтверждаться тестами :).


Название: Re: static_cast элементов в контейнере
Отправлено: Akon от Январь 21, 2015, 18:54
ViTech, спс. за интерес и конкретное предложение.
Код:
        const bool is_compatible_containers =
            std::is_same< _SourceContainerTemplate<_SourceItem, _SourceAlloc>
                        , _DestContainerTemplate<_SourceItem, _SourceAlloc>>::value
Вот это никогда не вычислится в true.

C контейнером работать не нужно, нужно работать с элементом контейнера. В контейнере в качестве элемента всегда указатель (по условию задачи). Далее, реинтерпрет будет безопасен тогда, когда не происходит смещения this (статик_каст осуществляет такое смещение). В рантайме это проверить элементарно, не знаю, возможно ли это в компайл-тайм.


Название: Re: static_cast элементов в контейнере
Отправлено: ViTech от Январь 21, 2015, 20:42
Код:
        const bool is_compatible_containers =
            std::is_same< _SourceContainerTemplate<_SourceItem, _SourceAlloc>
                        , _DestContainerTemplate<_SourceItem, _SourceAlloc>>::value
Вот это никогда не вычислится в true.
Для одного и того же типа контейнера будет true, в примере на это проверка и нацелена. Но это чтобы показать "в общем", если кому надо контейнеры "выцеплять", и, возможно, будут совместимые, между которыми можно преобразования делать. Для преобразований в рамках одного контейнера решение будет проще.

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

Но если уже начали писать хаки, то можно родить, например, следующий ужасный код:
Код
C++ (Qt)
const int dummy_object = 444555;
_SourceItem const source_item = (_SourceItem) dummy_object;
_DestItem const dest_item = static_cast<_DestItem> (source_item);
const bool is_compatible_inheritance = (int) dest_item == dummy_object;
static_assert(is_compatible_inheritance, "Incompatible inheritance");
Страшно? :) Да. Но в некоторых случаях работает. В частности на g++ 4.8.2. И даже ошибку правильную выдаёт в строке со static_cast, если оно не выполняется. А вот msvc 2012 такое собирать не хочет, возможно потому, что ещё не знает constexpr.


Название: Re: static_cast элементов в контейнере
Отправлено: _Bers от Январь 21, 2015, 20:43
Способ-хак с соответствующей проверкой (см. выше) как-раз гарантирует инвариант по указателю. Я рассматриваю в контейнере только указатели на объекты (см. выше), и в этом случае существенно:
1. размер эл-та контейнера остается тем-же;
2. проверка проверяет, что при касте не происходит смещения this.

Куда смотреть, на это:
Код:
std::vector<Base*>& bs = reinterpret_cast<std::vector<Base*>&>(ds);
?

Потому что эта хрень - мина замедленного действия.
После применения reinterpret_cast любые проверки уже избыточны.
Вам уже никто ничего не гарантирует.

Более того: сам reinterpret_cast никому ничего не гарантирует.
После его использования обеспечить гарантии становится невозможным.

Вам нужно понять одну вещь: вы не тип элемента кастите.
Вы тип самого вектора кастите к принципиально отличному типу.

Две вектора с разными типами по своему строению могут различаться, как небо и земля.
Все последствия только на вашей совести.







Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 21, 2015, 20:49
Две вектора с разными типами по своему строению могут различаться, как небо и земля.
Достаточно вспомнить о: vector<bool> и vector<T>,
где T - любой, исключая bool.


Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Январь 28, 2015, 08:33
Две вектора с разными типами по своему строению могут различаться, как небо и земля.
Не понимаю чем/как они могут отличаться. Вектор должен быть совместим с массивом

Код
C++ (Qt)
Base ** b = &vec1[0];
Derived ** d = &vec2[0];

Ну один тип указателя вместо другого, и что? Можно наворотить пример с множ наследованием где будет вылетать, но это надо постараться. А так не вижу чего же опасаться

Достаточно вспомнить о: vector<bool> и vector<T>,
где T - любой, исключая bool.
Расскажите, не читал  :)

Др дело что это как-то "нездорово", нет никакой защиты от добавления указателя на базовый - ну автор понимает этот риск


Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 28, 2015, 10:17
Расскажите, не читал  :)
std::vector<bool> хранит каждое значение в отдельном бите, т.е. его внутреннее устройство кардинально отличается std::vector<T>. Специализированные методы соответственно заточены для такого хранения. Если попробовать привести вектор bool к какому нибудь vector<char>, то получиться "каша".
Так же я могу специализировать любую коллекцию. Например, vector<Base*> будет стандартной реализации, а vector<Derived*> моей собственной, не имеющей ничего общего со стандартной.


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 28, 2015, 10:57
vector<Base*> и vector<Derived*> генерируют два совершенно разных класса. Только то, что они оба унаследованы от vector<void*> не приводит к ошибке.


Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 28, 2015, 11:10
vector<Base*> и vector<Derived*> генерируют два совершенно разных класса. Только то, что они оба унаследованы от vector<void*> не приводит к ошибке.
Как правило, устройство и внутренности у них одинаковы, поэтому и не приводит к ошибке. А я могу так специализировать вектор, что устройство и внутренности у него будут совершенно другие.


Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Январь 28, 2015, 12:31
std::vector<bool> хранит каждое значение в отдельном бите, т.е. его внутреннее устройство кардинально отличается std::vector<T>.
Не знал об этом, спасибо

Так же я могу специализировать любую коллекцию. Например, vector<Base*> будет стандартной реализации, а vector<Derived*> моей собственной, не имеющей ничего общего со стандартной.
Это предполагает непосредственное участие "злобного Буратино" - а без этого все одинаково, можно сказать "байт в байт". sizeof одинаков, конструктор/деструктор для указателей одинаков. Поэтому опасения преувеличены, хотя конечно конструкция "с душком"

vector<Base*> и vector<Derived*> генерируют два совершенно разных класса. Только то, что они оба унаследованы от vector<void*> не приводит к ошибке.
Во всяком случае здесь "унаследованы" - неверный термин


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 28, 2015, 13:05
Во всяком случае здесь "унаследованы" - неверный термин
Согласен.
Вот записки одного злого гения!
(http://i9.pixs.ru/storage/1/3/1/Straustrup_1170578_15796131.png)
(http://i9.pixs.ru/storage/1/3/7/Straustrup_9123757_15796137.png)


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 28, 2015, 13:08
А я могу так специализировать вектор, что устройство и внутренности у него будут совершенно другие.
А вы про указатели говорите? Как это сделать?


Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 28, 2015, 13:54
А вы про указатели говорите? Как это сделать?

Да как и с любым другим типом:
Код
C++ (Qt)
#include <vector>
#include <list>
#include <iostream>
 
template<class T>
class vec
{
public:
vec()
{
std::cerr << "vec::vec( T )" << std::endl;
}
 
std::vector<T> m_mem;
};
 
template<>
class vec<char*>
{
public:
vec()
{
std::cerr << "vec::vec( char * )" << std::endl;
}
 
std::list<char*> m_mem;
};
 
int main( int, char ** )
{
vec<void*> buf1;
vec<int*> buf2;
vec<char*> buf3;
 
return 0;
}
 

Цитировать
vec::vec( T )
vec::vec( T )
vec::vec( char * )


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 28, 2015, 14:07
Я подумал, что вы про класс вектор из библиотеки.


Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 28, 2015, 14:11
Я подумал, что вы про класс вектор из библиотеки.

 ::)

Код
C++ (Qt)
namespace std
{
 
template<>
class vector<char*>
{
public:
vector()
{
std::cerr << "vec::vec( char * )" << std::endl;
}
 
std::list<char*> m_mem;
};
 
}
 


Название: Re: static_cast элементов в контейнере
Отправлено: __Heaven__ от Январь 28, 2015, 14:12
::)
Я подозревал, что это последует :)


Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Январь 28, 2015, 15:27
Код
C++ (Qt)
namespace std
{
 
template<>
class vector<char*>
{
public:
vector()
{
std::cerr << "vec::vec( char * )" << std::endl;
}
 
std::list<char*> m_mem;
};
 
}
 
Правильно ли я понял что эта реализация НЕ может использовать ничего из vector <T>? (все требуемые методы надо писать самому)


Название: Re: static_cast элементов в контейнере
Отправлено: Old от Январь 28, 2015, 15:33
Правильно ли я понял что эта реализация НЕ может использовать ничего из vector <T>? (все требуемые методы надо писать самому)
Если вы внутри все делаете по своему, то конечно все методы (точнее все используемые методы) придется делать самому.


Название: Re: static_cast элементов в контейнере
Отправлено: Akon от Январь 28, 2015, 20:20
Код:
        const bool is_compatible_containers =
            std::is_same< _SourceContainerTemplate<_SourceItem, _SourceAlloc>
                        , _DestContainerTemplate<_SourceItem, _SourceAlloc>>::value
Вот это никогда не вычислится в true.
Для одного и того же типа контейнера будет true, в примере на это проверка и нацелена. ...
Согласен. Я почему то воспринял первый аргумент в DestContainerTemplate как _DestItem (где были мои глаза  :)).

Цитировать
const int dummy_object = 444555;
_SourceItem const source_item = (_SourceItem) dummy_object;
_DestItem const dest_item = static_cast<_DestItem> (source_item);
const bool is_compatible_inheritance = (int) dest_item == dummy_object;
static_assert(is_compatible_inheritance, "Incompatible inheritance");
Такого рода подход я и использовал для рантайм проверки. Уже не помню, почему не удалось в компайл-тайм. Уменя тогда были gcc 4.6 и msvc2010. Возможно, с gcc был компайл-тайм.

Друзья, я полностью согласен с тем, что частичной или полной специализацией можно изменить потроха контейнера, в результате реинтерпрет потеряет всякий смысл. Но я рассматриваю релевантные практике варианты.

Зайду с другой стороны - как вы реализуете метод MyContainer::items() в этом примере:
Код:
class Contaier;

class Item
{
public:
Container* container() { return container_; }

private:
Container* container_;
}

class Container
{
public:
vector<Item*> items() { return items_; }

protected:
void addItem(Item* item)
{
...
items_.push_back(item);
}

private:
vector<Item*> items_;
}

class MyContainer;

class MyItem : public Item
{
public:
MyContainer* container() { return static_cast<MyContainer*>(Item::container()); }
}

class MyContainer : public Container
{
public:
// ну а как вы поступите здесь с Container::items()?
vector<MyItem*> items() { return ??? }

void addItem(MyItem* item)
{
Container::addItem(item);
}
}
Все, что требуется в этом примере - это чтобы MyContainer возвращал MyItems тем же способом, как это делает Container.



Название: Re: static_cast элементов в контейнере
Отправлено: Igors от Февраль 01, 2015, 14:15
Код:
	// ну а как вы поступите здесь с Container::items()? 
vector<MyItem*> items() { return ??? }
Да просто
Код
C++ (Qt)
vector<MyItem*> items() { return (vector<MyItem*> &) Container::items(); }  // Waring: HACK
 
Провериться в compile/run-time по-моему недостижимо если базовый класс не полиморфный (vector<Item*>).