Open topin89 opened 2 years ago
если библиотека поддерживает итераторы, написание итератора-конвертера.
Хотел ответить, что можно написать только operator=(const T&)
и operator T()
.
C одной стороны, будет явное и безопасное соответствие между полями; с другой -- итераторы и алгоритмы должны сгенерироваться и оптимизироваться.
struct Coords {
int column;
int row;
void operator=(const XY& rhs) noexcept { column = rhs.x; row = rhs.y; }
operator XY() const noexcept { return XY{column, row}; }
};
Но компилятор в таком копировании не хочет разглядеть memcopy/memmove, и пишет код самостоятельно. https://godbolt.org/z/TaWsxdc6o
Так что решения не вполне эквивалентны, но выглядит больше как недоработка компилятора.
C одной стороны, будет явное и безопасное соответствие между полями; с другой -- итераторы и алгоритмы должны сгенерироваться и оптимизироваться.
Собственно, идея этого и ещё этого предложений в том, чтобы можно было обходиться без копирования массива, всего или поэлементно. И да, memcpy может оптимизироваться, а может и нет, и компилятор нам никогда не скажет, в чём проблема и заключена.
Это определённо оправдано, если тип, размер или выравнивание элементов структуры не совпадают. Но если это эффективно одно и то же, можно же дать как механизм как проверки, что struct{uint8_t r, g, b; };
ничем не отличается от std::array<uint8_t, 3>
, так и возможность избирательно алиасить такие типы и, желательно, все производные.
Есть три способа передать массив: итераторы/ranges, С-указатель, ссылка/указатель на C++ класс. Если что-то упустил, поправьте пожалуйста.
Последний способ отбрасываем за общей немасштабируемостью (нельзя передать вектор вместо массива).
С-указателю нужно ваше приведение. Шаблонный итератор-конвертер без него тоже не строится (нельзя взять указатель/ссылку на временный объект), согласен.
Если предположить, что у нас есть рефлексия вроде #482 или #468, то мы должны перестать считать такой код UB:
template<typename To, typename From>
To* dealiasing_cast(From* ptr)
{
static_assert(sizeof(To) == sizeof(From));
static_assert(alignof(To) == alignof(From));
static_assert(number_of_members<To> = number_of_members<From>);
for ... (auto i = 0; i < number_of_members<To>; ++i)
static_assert(std::is_same_v(type_of_member<To, i>, type_of_member<From, i>>);
return reinterpret_cast<To*>(ptr);
}
P1912: Types with array-like object representations
Круто! И думаю, можно слегка расширить, чтобы работала и на структурах типа таких:
struct PixOriented{
int x;
int y;
double theta;
};
struct oriented_point{
int x_offset;
int y_offset;
double theta;
};
Хотя шансов встретить такую структур существенно меньше, чем условные цвета и координаты, они есть. Уверен предложение можно расширить до условных:
union{
struct T layoutas(int[2], float[1]){
int x;
int y;
float z;
};
struct{
int array_int[2];
float array_float[1];
};
}
А внутри считать всё массивом из таких вот анонимных структур
Если структуры стандартно расположены то стандарт позволяет. Ещё можно посмотреть std::is_corresponding_member
https://www.reddit.com/r/cpp/comments/qs8ecs/is_there_a_way_to_reinterpret_cast_same_struct/
Сама идея
Хочется иметь возможность явно говорить компилятору, что указатель на структуру
можно считать указателям на эту:
Применение
Структуры вроде
lib1::Color
илиlib2::Coords
применяются во многих библиотеках, и как правило в них используются одни и те же типы, расположение и часто названия переменных. Это одни и те же структуры, и это одни и те же массивы структур. Ноreinterpret_cast<Coords*>(xy_pointer)
невозможен без нарушения Strict Aliasing. Следовательно, если результат одной библиотеки нужно передать как аргумент в другую, нужно сильно извернуться.Это или копирование всего массива, или, если библиотека поддерживает итераторы, написание итератора-конвертера. Или забить и писать
reinterpret_cast
и надеятся на лучшее. В конце концов, в clang'е прокатывает дажеКонечно, кроме случаев, когда не прокатывает.
Если библиотека поддерживает std::span на вход, было бы возможно сделать
но опять же, в момент взятия значения из B* Где-то в потрохах span'а, будет нарушения строгости и неопределённое поведение.
Ожидаемые проблемы
Само правило строгого разыменования взялось не от хорошей жизни. Тревис Доунс хорошо описал, а в PVS-Studio хорошо перевели, как отсутствие подобного ограничения влияет на банальный обход по вектору char'ов. И, если я правильно понял, сама возможность подобного преобразования может порушить часть оптимизаций.
Думаю, это можно обойти через std::launder (если я правильно понял механизм отмывания памяти) или разрешив только преобразование к указателям на константы.