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