3

I want to write an efficient way to write 0 and 1 in a byte (or any other type).

For instance, in C we can write something like:

uint8_t x = 0x00;
x|= (1 << 2) | (1 << 4);

to write 1 in bits 2 and 4. (of course, you don't use 2 and 4, but use macros to remember the meaning of bits 2 and 4).

I don't like these approach, so I write the following variadic template:

template<typename T>
T bitmask(T p0)
{
    return (1 << p0);
}

template<typename T, typename...Position>
T bitmask(T p0, Position... p1_n)
{
    return (1 << p0)|bit_mask(p1_n...);
}



template<typename T, typename... Position>
T& write_one(T& x, Position... pos0_n)
{
    x|= bit_mask(pos0_n...);

    return x;
}

and these work fine. You can write something like this:

uint8_t x = 0x00;
write_one(x, 2, 4);

But I'd prefered another solution. I'd like to write something like:

write_one<uint8_t>(x, 2, 4); // if x is uint8_t
write_one<uint16_t>(x, 2, 4); // if x is uint16_t

The type of write_one is the type of x (ok, I know you don't need to write the type uint8_t and uint16_t, I wrote it for clarity). The other parameters are always numbers (in fact, they are uint8_t).

How can I achieve these?

I want to write code like the following:

template<typename T>
T bitmask(uint8_t p0)
{
    return (1 << p0);
}

template<typename T>
T bitmask(T p0, uint8_t... p1_n)
{
    return (1 << p0)|bit_mask<T>(p1_n...);
}

template<typename T>
T& write_one(T& x, uint8_t... pos0_n)
{
    x|= bit_mask<T>(pos0_n...);

    return x;
}

Thank you very much.

Antonio
  • 579
  • 1
  • 3
  • 12

1 Answers1

4

Both of these methods produce exactly the same highly optimised assembler:

#include <utility>

template <int...bits, class Int>
constexpr auto set_bits_a(Int i)
{
    using expand = int[];
    void(expand{
        0,
        ((i |= (Int(1) << bits)),0)...
    });
    return i;
}

template <class Int, class...Bits>
constexpr auto set_bits_b(Int i, Bits...bits)
{
    using expand = int[];
    void(expand{
        0,
        ((i |= (Int(1) << bits)),0)...
    });
    return i;
}

int get_value();
volatile int x, y;

int main()
{
    x = set_bits_a<1, 3, 5>(get_value());
    y = set_bits_b(get_value(), 1, 3, 5);
}

output:

main:
        sub     rsp, 8
        call    get_value()
        or      eax, 42                  ; <-- completely optimised
        mov     DWORD PTR x[rip], eax
        call    get_value()
        or      eax, 42                  ; <-- completely optimised
        mov     DWORD PTR y[rip], eax
        xor     eax, eax
        add     rsp, 8
        ret
y:
x:

https://godbolt.org/g/CeNRVw

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thank you very much. But I don't understand the meaning of: void(expand{ 0, ((i |= (Int(1) << bits)),0)... }); Why is a casting to void? And the ..., in this case, what is it? – Antonio Jul 31 '17 at 12:23
  • @Antonio the `...` is called an elipsis. It's function is to expand the variadic argument. I cast to void to avoid compiler warnings about unused expressions. The use of `expand` is necessary until c++17 when we will have folding operators. – Richard Hodges Jul 31 '17 at 12:37