2

This compiles on Clang and GCC, but not MSVC:

#include <tuple>
#include <cstdint>
#include <cstdio>
#include <array>

template <uint32_t N>
struct BitfieldBits
{
    constexpr static inline uint64_t num_bits = N;
};

struct DepthMode : BitfieldBits<2>
{};
struct UseCulling : BitfieldBits<1>
{};
struct VertexShaderID : BitfieldBits<8>
{};


template <typename ... T>
struct MyBitfield
{
    uint64_t bitfield;

    static inline std::tuple<T...> tuple;

    constexpr static inline std::array<uint32_t, sizeof ... (T)> bit_shift_offsets = [](const std::tuple<T...>& tuple) consteval
        {

            constexpr uint32_t tuple_size = std::tuple_size_v<std::tuple<T...>>;

            std::array<uint32_t, tuple_size> arr;

            uint32_t bit_offset = 0;

            for (size_t i = 0; i < tuple_size; ++i)
            {

                arr[i] = bit_offset;
                
                // HERE MSVC DOESN'T COMPILE
                bit_offset += std::get<i>(tuple).num_bits;
            }


            return arr;

        }(std::tuple<T...>());

};


int main(int argc, char* argv[])
{
   
    MyBitfield<DepthMode, UseCulling, VertexShaderID> my_bitfield;
   
}

Here is the Godbolt link showing all three compilers.

And the error message given by MSVC is:

error C2672: 'get': no matching overloaded function found

2
  • my_bitfield.bit_shift_offsets always failed though. Commented Jun 8 at 5:29
  • @康桓瑋 Ah so it fails on all of them. Looks like I'll have to do this another way. Why does it fail though? The 'i' iterator should be known at compile time though. Commented Jun 8 at 5:33

1 Answer 1

5

i is not a constant expression in this loop:

for (size_t i = 0; i < tuple_size; ++i) {
    arr[i] = bit_offset;

    bit_offset += std::get<i>(tuple).num_bits;
                          ^^^
}

So all compilers will fail if you for example try:

auto& bso = MyBitfield<DepthMode, UseCulling, VertexShaderID>::bit_shift_offsets;

One way to assign the offsets in a way that is accepted by MSVC as well as the others is to manually unroll the loop using an index_sequence:

constexpr static inline std::array<uint32_t, sizeof...(T)>
    bit_shift_offsets = [](const std::tuple<T...>& tuple) consteval {
        return [&]<size_t... Is>(std::index_sequence<Is...>) {
            std::array<uint32_t, sizeof...(T)> arr;
            arr[0] = 0;
            (...,
                (arr[Is + 1] = arr[Is] + std::get<Is>(tuple).num_bits));
            return arr;
        }(std::make_index_sequence<sizeof...(T) - 1>{});
    }(std::tuple<T...>());

You can also simplify it by making it one lambda function only. You don't need an actual std::tuple<T...> instance but can extract num_bits using std::tuple_element_t like shown below:

constexpr static inline std::array<uint32_t, sizeof...(T)>
    bit_shift_offsets = []<size_t... Is>(std::index_sequence<Is...>) {
        std::array<uint32_t, sizeof...(T)> arr;
        arr[0] = 0;
        (...,
            (arr[Is + 1] =
                arr[Is] +
                std::tuple_element_t<Is, std::tuple<T...>>::num_bits));
        return arr;
    }(std::make_index_sequence<sizeof...(T) - 1>{});

An alternative using a loop that also supports sizeof...(T) being zero:

constexpr static inline std::array<uint32_t, sizeof...(T)>
    bit_shift_offsets = []<size_t... Is>(std::index_sequence<Is...>) {
        constexpr std::array<uint32_t, sizeof...(T)> tmp{
            std::tuple_element_t<Is, std::tuple<T...>>::num_bits...
        };
        std::array<uint32_t, sizeof...(T)> arr{};
        for (size_t i = 1; i < sizeof...(T); ++i) {
            arr[i] = arr[i - 1] + tmp[i - 1];
        }
        return arr;
    }(std::make_index_sequence<sizeof...(T)>{});

Another alternative supporting sizeof...(T) being zero, but without loops:

constexpr static inline std::array<uint32_t, sizeof...(T)>
    bit_shift_offsets = []() -> std::array<uint32_t, sizeof...(T)> {
        if constexpr (sizeof...(T) != 0) {
            return []<size_t... Is>(std::index_sequence<Is...>) {
                uint32_t bit_offset = 0;
                return std::array<uint32_t, sizeof...(T)>{
                    0, bit_offset +=
                    std::tuple_element_t<Is, std::tuple<T...>>::num_bits...};
            }(std::make_index_sequence<sizeof...(T) - 1>{});
        } else {
            return {};
        }
    }();
Sign up to request clarification or add additional context in comments.

3 Comments

With std::tuple_element_t that's being able to index into the type directly. I see they added indexing into parameter packs in C++26. I guess that's different because you index to get the object, not the type. Can you do this in pre c++26?
@Zebrafish std::tuple_element_t<Is, std::tuple<T...>> instantiates the template std::tuple<T...> without creating an object of that type or without creating any of the T... objects either.
@Zebrafish Here's a small example: godbolt.org/z/nfj4n78na - note that neither foo nor bar are actually created.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.