Следующее работает для размеров 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;
}