Как мне вставить значение в «середину» другого? - PullRequest
0 голосов
/ 28 апреля 2019

У меня есть два значения v1 и v2 типов T1 и T2 соответственно, с sizeof (T1)> sizeof (T2).Оба типа являются обычными старыми данными.Теперь я хочу заменить k'th, k + 1'th, ... k + sizeof (T2) -1'-й байт v1 байтами v2.

C ++ не предлагаетэта функциональность присуща ни языку, ни моим знаниям в стандартной библиотеке (по крайней мере, не напрямую).Каков наилучший подход к реализации этого в целом?т. е. реализация:

template<typename T1, typename T2>
void replace_bytes(T1& v1, T2 v2, std::size_t k)

или, по крайней мере,

template<typename T1, typename T2, std::size_t k>
void replace_bytes(T1& v1, T2 v2)

?

Мои мысли были:

  1. Переосмысление приведено в массивыbytes
  2. Переинтерпретировать приведение в std :: массив байтов
  3. Использовать spans
  4. Арифметика указателя с адресом v1
  5. Для не слишком больших типов- переинтерпретировать как целые числа без знака и использовать битовые операции: И с маской, сдвигом, ИЛИ для объединения существующих и замещающих битов.

Примечания:

  • Конечно, если k слишком высоко, здесь будет UB (или мы могли бы убедиться, что оно не слишком высоко).
  • Для простоты вы можете предположить, что расположение памяти имеет младший порядок.
  • Если выравнивание является проблемой, четко определите свой выбор.
  • Эффективность / скорость, конечно, является ключевой проблемой.
  • Если ваше предложение требует более нового стандарта языка C ++, это нормально,но упоминайте об этом.
  • Важно, чтобы код был хорошо оптимизирован во время компиляции.Лайон.

Ответы [ 2 ]

2 голосов
/ 28 апреля 2019
// needed include files
#include <iostream>  // for cout
#include <stdexcept> // for runtime_error
#include <cstring>   // for memcpy

// generic template function that takes 3 arguments
// 1 destination object
// 2 source object
// 3 from which byte to start in the destination
template<class T1, class T2>
void replace_bytes ( T1& t1, const T2& t2, std::size_t k )
{
// at compile time, store the size of T1 type in t1_size
   constexpr std::size_t t1_size = sizeof(T1);
// at compile time, store the size of T2 type in t2_size
   constexpr std::size_t t2_size = sizeof(T2);
// if we copy t2 bytes to t1, do we run out of memory ?
   if ( k + t2_size > t1_size )
   {
       throw std::runtime_error("Can't copy out of bounds.");
   }
// do the copying, casting is required for proper pointer arithmitic
   std::memcpy( (void*) (((char*)&t1)+k), (const void*) &t2, t2_size );
}

int main()
{
  int x = 0;
  char c = 10;
  replace_bytes(x, c, 0);
  std::cout << x << std::endl;
}

Чистая версия кода (без комментариев):

#include <iostream>
#include <stdexcept>
#include <cstring>

template <class T1, class T2>
void replace_bytes ( T1& t1, const T2& t2, std::size_t k )
{
   constexpr std::size_t t1_size = sizeof(T1);
   constexpr std::size_t t2_size = sizeof(T2);

   if ( k + t2_size > t1_size )
   {
       throw std::runtime_error("Can't copy out of bounds.");
   }
   std::memcpy( (void*) (((char*)&t1)+k), (const void*) &t2, t2_size );
}

int main()
{
  int x = 0;
  char c = 10;
  replace_bytes(x, c, 0);
  std::cout << x << std::endl;
}
0 голосов
/ 30 апреля 2019

Следующее работает для размеров T1 размером до 8 байт, и кажется достаточно хорошо оптимизированным на GCC, clang и MSVC - по крайней мере, при вставке:

namespace detail {

template <unsigned NBytes> struct uint;

template<> struct uint<1> { using type = uint8_t;  };
template<> struct uint<2> { using type = uint16_t; };
template<> struct uint<4> { using type = uint32_t; };
template<> struct uint<8> { using type = uint64_t; };

} // namespace detail

template <unsigned NBytes>
using uint_t = typename detail::uint<NBytes>::type;

template <typename T1, typename T2>
inline void replace_bytes(T1& v1 ,T2 v2, std::size_t k)
{
    static_assert(sizeof(T1) > sizeof(T2), "invalid sizes");
    static_assert(std::is_trivial<T1>::value, "T1 must be a trivial type");
    static_assert(std::is_trivial<T2>::value, "T2 must be a trivial type");
    auto shift_amount = k * CHAR_BIT;
    using uint_1_t = uint<sizeof(v1)>;
    using uint_2_t = uint<sizeof(v2)>;
    auto& v1_as_uint = *reinterpret_cast<uint_1_t*>(&v1);
    const auto& v2_as_uint = *reinterpret_cast<uint_2_t*>(&v2);

    auto v1_mask = ~( (uint_1_t{1} << (sizeof(T2) * CHAR_BIT) - 1) << shift_amount);
    auto shifted_v2 = uint_1_t{v2_as_uint} <<  shift_amount;
    v1_as_uint = (v1_as_uint & v1_mask ) | shifted_v2;
}

но я чувствую, что лучше избегать out-параметров - и действительно, это позволяет реализации функции быть строго в регистрах :

template <typename T1, typename T2>
T1 replace_bytes(T1 v1 ,T2 v2, std::size_t k)
{
    static_assert(sizeof(T1) > sizeof(T2), "invalid sizes");
    static_assert(std::is_trivial<T1>::value, "T1 must be a trivial type");
    static_assert(std::is_trivial<T2>::value, "T2 must be a trivial type");
    auto shift_amount = k * CHAR_BIT;
    using uint_1_t = uint_t<sizeof(v1)>;
    using uint_2_t = uint_t<sizeof(v2)>;
    auto& v1_as_uint = *reinterpret_cast<uint_1_t*>(&v1);
    const auto& v2_as_uint = *reinterpret_cast<uint_2_t*>(&v2);

    auto v1_mask = ~( (uint_1_t{1} << (sizeof(T2) * CHAR_BIT) - 1) << shift_amount);
    auto shifted_v2 = uint_1_t{v2_as_uint} <<  shift_amount;
    return (v1_as_uint & v1_mask ) | shifted_v2;
}
...