How should I insert a value into the “middle” of a

2019-08-19 01:22发布

I have two values v1 and v2 of types T1 and T2 respectively, with sizeof(T1)>sizeof(T2). Both types are plain-old-data. Now, I want to replace the k'th, k+1'th, ... k+sizeof(T2)-1'th bytes of v1 with the bytes of v2.

C++ doesn't offer this functionality inherently in the language, nor to my knowledge in the standard library (at least, not directly). What would be the best approach to implementing this generically? i.e. implementing:

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

or at least

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

?

My thoughts have been:

  1. Reinterpret cast into arrays of bytes
  2. Reinterpret cast into std::array of bytes
  3. Use spans
  4. Pointer arithmetic with the address of v1
  5. For not-so-large types - reinterpret as unsigned integers and use bit operations: AND with a mask, shift, OR to combine the existing and replacement bits.

Notes:

  • Of course if k is too high there will be UB here (or we could check it isn't too high).
  • You may assume for the sake of simplicity that memory layout is little-endian.
  • If alignment is an issue, be explicit about your choices regarding it.
  • Efficiency/speed is of course a key issue.
  • If your suggestion requires a newer C++ language standard, that's fine, but do mention it.
  • It is important for the code be well-optimized during compilation.

2条回答
The star\"
2楼-- · 2019-08-19 01:57
// 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;
}

Clean version of code (no comments):

#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;
}
查看更多
贼婆χ
3楼-- · 2019-08-19 02:05

The following works for T1 sizes upto 8 bytes, and seems to get optimized well enough on GCC, clang and MSVC - at least when inlining:

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;
}

but I feel it's better to avoid out-parameters - and indeed, doing so allows the function implementation to be strictly in-registers:

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;
}
查看更多
登录 后发表回答