Variadic template variable parameter pack expansion in recursive variable definition has incorrect size

huangapple go评论65阅读模式
英文:

Variadic template variable parameter pack expansion in recursive variable definition has incorrect size

问题

在开发低级网络设备驱动程序时,我需要进行许多位字段操作,涉及到掩码、位移等,以设置/清除各种控制寄存器中的控制位。由于有许多寄存器和许多要初始化的字段,通过C++的位与(&)、位或(|)和位左移(<<)运算符来管理位字段的规范方式会导致代码非常容易出错,难以阅读和维护。

我想出了一个创意,创建一个constexpr全局常量(MASK_1),它接受一组位字段大小和偏移量,并生成定义字段的64位常量掩码。例如:

auto static constexpr mask = MASK_1 <0, 4, 60, 4>; // mask = 0xF00000000000000F

在尝试实现这个想法时,我遇到了一个似乎是编译器中的错误的编译时错误。

我第一次尝试定义MASK_1变量时使用了如下的可变模板变量:

template <unsigned LSB, unsigned NB, unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;

这种形式在C++20中使用参数包在fold表达式中进行左侧初始化,据我所知,是有效的。

尝试编译这个会产生一个错误消息,说递归引用的参数包太短。我已经确认我的测试案例有确切的2、4或6个参数,导致参数包参数为0、2或4,因此预期的参数数量没有不匹配。

我尝试按以下方式重新编写MASK_1常量,使用lambda函数,它工作正常:

template <unsigned LSB, unsigned NB, unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = [] (void) noexcept -> uint64_t
{
  auto const mask_1 = (uint64_t (-1) >> 64 - NB) << LSB ;

  if constexpr  (sizeof ... (LSB_NB_LIST) == 0)
    {
      return mask_1 ;
    }
  else
    {
      return mask_1 | MASK_1 <LSB_NB_LIST...> ;
    }
} () ;

我还尝试在第二次尝试中使用fold表达式,但它也生成错误:

template <unsigned LSB, unsigned NB, unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = [] (void) noexcept -> uint64_t
{
  auto const mask_1 = (uint64_t (-1) >> 64 - NB) << LSB ;

  if constexpr  (sizeof ... (LSB_NB_LIST) == 0)
    {
      return mask_1 ;
    }
  else
    {
      return (mask_1 | ... | MASK_1 <LSB_NB_LIST>) ;
    }
} () ;

此时,似乎我的编译器(GCC 11.2.0)存在错误。

是否有人可以确认这是否是编译器错误,或者我在第一次和第三次尝试中是否以某种方式错误地使用了fold表达式语法?

编辑:

这是最小的测试代码:

#include <cstdio>
#include <cstdint>

template <unsigned LSB, unsigned NB, unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;

int main (int argc , char * args [])
{
  printf ("%016llX\n" , MASK_1 <0 , 4>) ;          // Works

  printf ("%016llX\n" , MASK_1 <0 , 4 , 60 , 4>) ; // Error
}

这是编译器调用:

g++ --std=c++20 -I. -o m.exe main.cpp

这是编译器错误消息:

main.cpp: In instantiation of 'constexpr const uint64_t MASK_1<0, 4, 60, 4>':
main.cpp:12:25:   required from here
main.cpp:6:74: error: wrong number of template arguments (1, should be at least 2)
    6 | constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;
      |                                                                          ^~~~~~~~~~~~~~~~~~~~
main.cpp:6:20: note: provided for 'template<unsigned int LSB, unsigned int NB, unsigned int ...LSB_NB_LIST>  requires 1 <= NB && NB <= 64 && LSB + NB <= 64 && sizeof ... ((LSB_NB_LIST ...)) % 2 == 0 constexpr const uint64_t MASK_1<LSB, NB, LSB_NB_LIST ...>'
    6 | constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;
      |                    ^~~~~~

编辑-2:工作的Lambda方法

#include <cstdio>
#include <cstdint>

template <unsigned LSB, unsigned NB, unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

#if 0

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;

#else

constexpr uint64_t MASK_1 = [] (void) noexcept

<details>
<summary>英文:</summary>

While working on a low-level network device driver, I needed to do numerous bit-field operations involving masking, shifting, etc. in order to set/clr control bits in various control registers.  As there are many registers and many fields to initialize, the canonical way of managing bit fields via C++ &amp;, |, and &lt;&lt; operators leads to code that is very error prone and difficult to read and maintain.

I came up with the idea to create a constexpr global constant (MASK_1) that accepts a list of bit-field sizes and offsets and generates a 64-bit constant mask for the defined fields.  For example :

`auto static constexpr mask = MASK_1 &lt;0 , 4 , 60 , 4&gt; ; // mask = 0xF00000000000000F` 

While attempting to implement this idea, I get a compile-time error that seems like a bug in the compiler.


My first attempt at defining the MASK_1 variable was to use a variadic template variable of the following :


template <unsigned LSB , unsigned NB , unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;


This form uses the parameter pack in a fold expression with a left-side initialization and, to my knowledge, is valid in C++20.  

Trying to compile this yields an error message that the recursive reference parameter pack is too short.  I have confirmed my test cases to have exactly 2, 4, or 6 arguments leading to 0 , 2 , or 4 parameter pack arguments, so there is no mismatch in the number of expected arguments.

I tried rewriting the MASK_1 constant using a lambda according to the following and it works fine :


template <unsigned LSB , unsigned NB , unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = [] (void) noexcept -> uint64_t
{
auto const mask_1 = (uint64_t (-1) >> 64 - NB) << LSB ;

if constexpr (sizeof ... (LSB_NB_LIST) == 0)
{
return mask_1 ;
}
else
{
return mask_1 | MASK_1 <LSB_NB_LIST ...> ;
}
} () ;


I also tried to use a fold expression in the second attempt as below, but it too generates an error :


template <unsigned LSB , unsigned NB , unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = [] (void) noexcept -> uint64_t
{
auto const mask_1 = (uint64_t (-1) >> 64 - NB) << LSB ;

if constexpr (sizeof ... (LSB_NB_LIST) == 0)
{
return mask_1 ;
}
else
{
return (mask_1 | ... | MASK_1 <LSB_NB_LIST>) ;
}
} () ;


At this point, it appears that there is a bug in my compiler (GCC 11.2.0).

Can anyone confirm if this is a compiler bug or if I am somehow incorrectly doing the fold expression syntax in the first and third attempts?

EDIT:

Here is the minimal test code :

#include <cstdio>
#include <cstdint>

template <unsigned LSB , unsigned NB , unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;

int main (int argc , char * args [])
{
printf ("%016llX\n" , MASK_1 <0 , 4>) ; // Works

printf ("%016llX\n" , MASK_1 <0 , 4 , 60 , 4>) ; // Error
}

Here is the compiler call :

g++ --std=c++20 -I. -o m.exe main.cpp

Here is the compiler error message :

main.cpp: In instantiation of 'constexpr const uint64_t MASK_1<0, 4, 60, 4>':
main.cpp:12:25: required from here
main.cpp:6:74: error: wrong number of template arguments (1, should be at least 2)
6 | constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;
| ^~~~~~~~~~~~~~~~~~~~
main.cpp:6:20: note: provided for 'template<unsigned int LSB, unsigned int NB, unsigned int ...LSB_NB_LIST> requires 1 <= NB && NB <= 64 && LSB + NB <= 64 && sizeof ... ((LSB_NB_LIST ...)) % 2 == 0 constexpr const uint64_t MASK_1<LSB, NB, LSB_NB_LIST ...>'
6 | constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;
| ^~~~~~

EDIT-2 : Lambda method that works

#include <cstdio>
#include <cstdint>

template <unsigned LSB , unsigned NB , unsigned ... LSB_NB_LIST> requires (1U <= NB && NB <= 64U) && (LSB + NB <= 64U) && (sizeof ... (LSB_NB_LIST) % 2U == 0U)

#if 0

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;

#else

constexpr uint64_t MASK_1 = [] (void) noexcept -> uint64_t
{
auto const mask_1 = (uint64_t (-1) >> 64 - NB) << LSB ;

if constexpr (sizeof ... (LSB_NB_LIST) == 0)
{
return mask_1 ;
}
else
{
return mask_1 | MASK_1 <LSB_NB_LIST ...> ;
}
} () ;

#endif

int main (int argc , char * args [])
{
printf ("%016llX\n" , MASK_1 <0 , 4>) ; // Works

printf ("%016llX\n" , MASK_1 <0 , 4 , 60 , 4>) ; // Works
}




</details>


# 答案1
**得分**: 0

抱歉,这段内容涉及到特定的代码语法和结构,不适合进行中文翻译。如果您需要有关代码的帮助或解释,请随时提出具体问题,我会尽力协助您。

<details>
<summary>英文:</summary>

The issue here, as I have come to understand, is a mismatch between what I **think** should be happening vs what is **actually** happening.

The original expression :

constexpr uint64_t MASK_1 = (((uint64_t (-1) >> 64 - NB) << LSB) | ... | MASK_1 <LSB_NB_LIST>) ;


This is actually doing the following :

constexpr uint64_t MASK_1 = (uint64_t (-1) >> 64 - NB) << LSB | MASK_1 <LSB_1> | MASK_1 <NB_1> | MASK_1 <LSB_2> | MASK_1 <NB_2> | ... ;

instead of what I **thought** it was supposed to do :

constexpr uint64_t MASK_1 = ((uint64_t (-1) >> 64 - NB) << LSB | MASK_1 <LSB_1 , NB_1> | MASK_1 <LSB_2 , NB_2> | ... ;

Which is why the compiler emits the correct error message stating the wrong number of arguments for MASK_1 in the binary fold expression.

For the lambda method, the recursive call to MASK_1 actually passes the whole parameter pack, so is correct.



  [1]: https://stackoverflow.com/users/775806/n-m

</details>



huangapple
  • 本文由 发表于 2023年5月30日 05:17:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76360361.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定