Russian Qt Forum
Май 03, 2024, 20:53 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: static_cast элементов в контейнере  (Прочитано 12542 раз)
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);  // ОК
без синтаксического оверхеда.

Есть ли где-то что-то подобное (в бусте, может, - давно туда не заглядывал) с необходимыми проверками, с перегрузкой под константность?
« Последнее редактирование: Январь 18, 2015, 16:32 от Akon » Записан
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2130



Просмотр профиля
« Ответ #1 : Январь 19, 2015, 00:08 »

Мне кажется, что это какой-то хард кор.
На мой взгляд, необходимо ручками перетащить элементы из одного контейнера в другой. Ну или написать функцию, если хочется сделать это в одну строчку.
То, что вы пытаетесь сделать, выглядит как-то грубо и по сути вы преобразуете class Heaven в class Earth. То, что это как-то работает через reinterpret cast - заслуга std::vector<void*>.
Но это просто мое мнение.
Записан
Akon
Гость
« Ответ #2 : Январь 19, 2015, 10:38 »

Это как раз для того, чтыбы избежать "необходимо ручками перетащить элементы из одного контейнера в другой. Ну или написать функцию, если хочется сделать это в одну строчку.". Смысл в отсутствии ненужных вычислений - это же С/С++, а не Java или C# Улыбающийся

Я прекрасно понимаю, что происходит "за кулисами". Осознанно пользуюсь этим подходом.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Январь 19, 2015, 10:46 »

Мне кажется, что это какой-то хард кор.
"Простое" решение - работать с vector <Base *>, но тогда выплывает масса приведений после взятия эл-та. А делать везде vector <Derived *> еще хуже, все кишки наружу. Я унаследовался от контейнера и сохранил в нем ID типа явно - чтобы отловить недопустимое приведение в runtime. Но все равно привожу
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #4 : Январь 19, 2015, 13:13 »

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

За буст не скажу, но можно попробовать написать container_items_static_cast самому, взяв в качестве основного преобразования reinterpret_cast, а проверку типов выполнять с помощью type_traits.
Записан

Пока сам не сделаешь...
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Январь 19, 2015, 13:32 »

За буст не скажу, но можно попробовать написать container_items_static_cast самому, взяв в качестве основного преобразования reinterpret_cast, а проверку типов выполнять с помощью type_traits.
Не вижу как это сработает если контейнер пуст. А непустой - так проще банальное dynamic_cast
Записан
Akon
Гость
« Ответ #6 : Январь 19, 2015, 15:01 »

Разумеется, дополнительные проверки необходимы, а точнее, требуется только, чтобы "... Base есть "первая база" Derived". Когда-то (2-3 года назад) наспех я сделал общее решение (на шаблонах) проверки первой базы, но оно, если мне не изменяет память, работало в рантайме, и type_traits тогда мне не помог. Сейчас в фоне я повторно интересуюсь проблемой.
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #7 : Январь 21, 2015, 08:38 »

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

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


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

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

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

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

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

Любая реализация снаружи - только на свой страх и риск.
Записан
Akon
Гость
« Ответ #8 : Январь 21, 2015, 10:33 »

Способ-хак с соответствующей проверкой (см. выше) как-раз гарантирует инвариант по указателю. Я рассматриваю в контейнере только указатели на объекты (см. выше), и в этом случае существенно:
1. размер эл-та контейнера остается тем-же;
2. проверка проверяет, что при касте не происходит смещения this.
Записан
ViTech
Гипер активный житель
*****
Offline Offline

Сообщений: 858



Просмотр профиля
« Ответ #9 : Январь 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)? И как относиться к контейнеру, который указатели на базовые и производные объекты хранит по разному?

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

Пока сам не сделаешь...
Akon
Гость
« Ответ #10 : Январь 21, 2015, 18:54 »

ViTech, спс. за интерес и конкретное предложение.
Код:
        const bool is_compatible_containers =
            std::is_same< _SourceContainerTemplate<_SourceItem, _SourceAlloc>
                        , _DestContainerTemplate<_SourceItem, _SourceAlloc>>::value
Вот это никогда не вычислится в true.

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

Сообщений: 858



Просмотр профиля
« Ответ #11 : Январь 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.
Записан

Пока сам не сделаешь...
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #12 : Январь 21, 2015, 20:43 »

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

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

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

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

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

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





Записан
Old
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4349



Просмотр профиля
« Ответ #13 : Январь 21, 2015, 20:49 »

Две вектора с разными типами по своему строению могут различаться, как небо и земля.
Достаточно вспомнить о: vector<bool> и vector<T>,
где T - любой, исключая bool.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Январь 28, 2015, 08:33 »

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

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

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

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

Др дело что это как-то "нездорово", нет никакой защиты от добавления указателя на базовый - ну автор понимает этот риск
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.275 секунд. Запросов: 23.