c++visual-c++constexpr

Why won't MSVC allow me to index into this array in consteval function?


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


Solution

  • 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 {};
            }
        }();