Constexpr Variadic Template to reorder unsigned integers

Viewed 118

I'm working with a peripheral which sends lowest 8 bit word first. So the first word (in this case 16bit are send) has to be on the right hand side. As seen in variable v1.

I would like to have a more expressive version of this so I wanted to add a constexpr function that shifts the first parameter on the right most position of the resulting integer. While still using it as the first parameter of the function.

I didn't really came up with a good solution. As I build up a "shift value" and decrease it upon leaving the recursion. Also, this is not working in a constexpr environment, which is for me a "no go". Maybe someone of you has a suggestion ?

I did try a lot of things. But mostly not working. fold expressions also didn't really help. Cpp Version doesn't matter (c++2a is also okay)

// send 0x05 first then 0xFF
std::uint16_t v1 = (0xFFU << 8U | 0x05U); // right align first 8bit word so it is send first
std::uint16_t v2 = lower_word_first(0x05U, 0xFFU); 

// lower_word_first

static std::size_t shift = 0;

auto lower_word_first(){
    return 0;
}

template<typename unsigned_word_type, typename... words>
auto lower_word_first(unsigned_word_type word, words... ws){
    shift += 1;
    auto val = lower_word_first(ws...); // just for debugging purposes split into val
    auto res = val | word << (shift - 1) * 8U;
    shift -= 1;
    return res;
}
1 Answers
#include <cstddef>
#include <utility>

template <std::size_t... Is, typename... Ws>
constexpr auto lower_word_first(std::index_sequence<Is...>, Ws... ws)
{
    return ((ws << Is*8) | ...);
}

template <typename... Ws>
constexpr auto lower_word_first(Ws... ws)
{
    return lower_word_first(std::index_sequence_for<Ws...>{}, ws...);
}

DEMO

Here, std::index_sequence_for, e.g.

std::index_sequence_for<A, B, C, D>

produces:

std::index_sequence<0, 1, 2, 3>

Then the fold-expression becomes:

(ws_0 << 0*8) | (ws_1 << 1*8) | (ws_2 << 2*8) | (ws_3 << 3*8)

Alternatively, store the arguments in an array, and iterate in reversed direction.

With some features, and a helper function to force a specific type, this can look like:

#include <cstdint>
#include <array>
#include <ranges>
#include <concepts>

template <typename T>
auto type()
{
    if constexpr (std::is_same_v<T, std::uint8_t>) return std::uint16_t{};
    if constexpr (std::is_same_v<T, std::uint16_t>) return std::uint32_t{};
    if constexpr (std::is_same_v<T, std::uint32_t>) return std::uint64_t{};
}

template <std::integral W>
constexpr auto lower_word_first(W w, std::same_as<W> auto... ws)
{
    decltype(type<W>()) val{};
    const std::array a = { w, ws... };
    for (auto v : std::ranges::reverse_view{ a }) {
        val <<= 8;
        val |= v;
    }
    return val;
}

DEMO 2

Related