英文:
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++ &, |, and << 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 <0 , 4 , 60 , 4> ; // 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>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论