c++templatesc++20index-sequence

How to use index sequence to unroll for loops?


I am trying to convert unsigned integral data to their in memory binary representation as efficiently as possible.

I wrote four template functions to convert the integers to both little endian and big endian, two of them use bit operations, and the other two use pointers to copy data.

They are verified to be correct, and also very efficient, as I have determined that the little endian functions to be as fast as std::memcpy, but the big endian functions somehow take a little bit longer.

These functions are:

#include <vector>

using std::vector;
typedef vector<uint8_t> bytes;

template<class T>
inline bytes LittleEndian(const T& data) {
    size_t size = sizeof(T);
    bytes _bytes(size);
    uint8_t mask = 255;
    for (size_t i = 0, shift = 0; i < size; i++, shift += 8) {
        _bytes[i] = (data >> shift) & mask;
    }
    return _bytes;
}

template<class T>
inline bytes BigEndian(const T& data) {
    size_t size = sizeof(T);
    bytes _bytes(size);
    uint8_t mask = 255;
    for (size_t i = size, shift = 0; i-- > 0; shift += 8) {
        _bytes[i] = (data >> shift) & mask;
    }
    return _bytes;
}

template<class T>
inline bytes CPU_Endian(const T& data) {
    size_t size = sizeof(T);
    bytes _bytes(size);
    uint8_t* dst = (uint8_t *)_bytes.data(), * src = (uint8_t *) & data;
    for (size_t i = 0; i < size; i++) {
        *dst++ = *src++;
    }
    return _bytes;
}

template<class T>
inline bytes Flip_CPU_Endian(const T& data) {
    size_t size = sizeof(T);
    bytes _bytes(size);
    uint8_t* dst = (uint8_t *)_bytes.data(), * src = (uint8_t *)&data + size - 1;
    for (size_t i = 0; i < size; i++) {
        *dst++ = *src--;
    }
    return _bytes;
}

And I want to unroll the for loops using std::index_sequence, and because they are related so I put them in one question. They are about three things: do something repeatedly N times, making an index sequence that decreases rather than increases, and using the index to set values.

I have tried to do it myself but that doesn't work:

template<class T>
inline bytes CPU_Endian2(const T& data) {
    size_t size = sizeof(T);
    bytes _bytes(size);
    uint8_t* dst = (uint8_t*)_bytes.data(), * src = (uint8_t*)&data;
    [&]<std::size_t...N>(std::index_sequence<N...>){
        ((*dst++ = *src++),...);
    }(std::make_index_sequence<size>{});
    return _bytes;
}

It doesn't compile, error log:

Build started at 18:54...
1>------ Build started: Project: hexlify_test, Configuration: Release x64 ------
1>hexlify_test.cpp
1>C:\Users\Estranger\source\repos\hexlify_test\hexlify_test.cpp(98,3): error C7515: a fold expression must contain an unexpanded parameter pack
1>C:\Users\Estranger\source\repos\hexlify_test\hexlify_test.cpp(99,3): error C3878: syntax error: unexpected token '(' following 'expression'
1>C:\Users\Estranger\source\repos\hexlify_test\hexlify_test.cpp(99,3): message : error recovery skipped: '( identifier ::  . . . {'
1>C:\Users\Estranger\source\repos\hexlify_test\hexlify_test.cpp(99,35): error C2760: syntax error: '}' was unexpected here; expected ';'
1>Done building project "hexlify_test.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
========== Build completed at 18:54 and took 01.796 seconds ==========

How can I convert these functions to ones that use std::index_sequence instead of for loops?


Adding constexpr to size_t size = sizeof(T); failed to make it compile.


Solution

  • You have 2 issues:

    size is not constexpr, so cannot be used as template argument.

    -> constexpr size_t size = sizeof(T);

    Your fold expression doesn't use any pack

    So either

    (((*dst++ = *src++), static_cast<void>(Is)), ...);
    

    or

    ((dst[Is] = src[Is]),...);
    

    Together, it would be

    template<class T>
    bytes CPU_Endian2(const T& data) {
        constexpr size_t size = sizeof(T);
        bytes _bytes(size);
        uint8_t* dst = (uint8_t*)_bytes.data(), * src = (uint8_t*)&data;
        [&]<std::size_t...Is>(std::index_sequence<Is...>){
            ((dst[Is] = src[Is]), ...);
        }(std::make_index_sequence<size>{});
        return _bytes;
    }
    

    Demo