提问人: 提问时间:8/23/2023 更新时间:8/23/2023 访问量:149
如何在 cpp 中实现可读的模板 for 循环语法?
How to implement a readable template for-loop syntax in cpp?
问:
我想编写一些头文件,其中包含这些文件允许我以可访问的语法编写模板 for 循环。我该如何实现?(参见底部为我最好的尝试)。
下面是一个示例和标准,说明可访问语法的含义。
例:
for_loop<int,i0,ic,iE>(i){ // for(int i=i0;i<iE;i+=ic), where i0,ic,iE are constexpr int
foo(i);
bar<i>();
for_loop<int,i,1,10>(j){
boo<i,j>();
}
}
标准
正如该示例所满足的,可访问语法的有用条件是:
- 每个 for 循环的范围都有支撑
- 没有样板(如创建 lambda 函数、函子类、索引序列、令人困惑的工件,如 、 ,它们既增强了循环范围的可读性,也没有增强循环体的可读性等)
()
[]
,...
- 在支撑作用域内变量和函数的可访问性与经典的 for 循环相同。
- desirable:使用 as 的表示法将它们指示为模板参数。// 使用 (i
)
是因为我假设我们需要一个宏来表示符号i
<>
int,i0,ic,iE
相关问题如下:
- [https://stackoverflow.com/questions/13816850/is-it-possible-to-develop-static-for-loop-in-c]
- [https://stackoverflow.com/questions/1032602/template-ing-a-for-loop-in-c]
- [https://stackoverflow.com/questions/42019564/how-to-iterate-over-stdindex-sequence]
然而,这些问题的重点是功能的实现。相反,这个问题的重点明确地放在实现可访问的语法上。
最佳尝试
以下是我为实现解决方案而做出的最佳功能尝试:
# define template_for_loop_begin(index) \
[&]<std::size_t... index>(std::index_sequence<index...>) {
# define template_for_loop_end(index,N) ;}(std::make_index_sequence<N>{});
// this allows writing a template for-loop as follows:
template_for_loop_begin(i)(
bar<i>(),...
)template_for_loop_end(i,10)
但是,生成的语法并不令人满意。问题是:
- Major:循环体限制为一行
- major:循环体必须是单个函数调用
- semi:有,而不是在行的末尾
,...
;
- semi:索引位于循环的页脚中
- 次要的环括号代替了
()
{}
Normal For 循环示例及其带来的问题
真实世界的代码示例是以下客户的代码,使用我的样条库。
for(int i=0;i<1000;++i){
int j=i;
spline.load(j,data[j]);
}
这工作正常。现在一些员工已经有了坏主意,比如:
for(int i=0;i<500;++i){
if(data[i]>0){ j=i+500;}else{j=i;}
spline.load(j,data[i]);
}
我的软件使用基于生成源代码的运行时的自动代码转换。为了使此源代码转换产生我的预期行为,索引必须是一个 constexpr。因此,我想把它做成模板。但是,我无法教育客户重新学习一种编写 for 循环的方式,从而阻止/抑制他们使用我的代码。这就引出了这个问题。j
模板、Consteval 和 Consteffr 以及类型
感谢 Yakk - Adam Nevraumont 提供的善意答案,我们可以使用以下内容:static_foreach
#include <utility>
template<auto first, auto increment, auto limit>
struct static_foreach_t {
void operator->*( auto f ) const& {
[&]<auto...Is>(std::integer_sequence<decltype(first), Is...>) {
( f( std::integral_constant<decltype(Is), first+Is*increment>{} ), ...);
}( std::make_integer_sequence< decltype(first), (limit-first)/increment >{} );
}
};
template<auto first, auto increment, auto limit>
constexpr static_foreach_t<first, increment, limit> static_foreach = {};
// ++++
template<typename T>
struct Cxpr{
const T t;
consteval Cxpr(T t):t(t){}
};
void foo(int){}
void bar(Cxpr<int>){}
template<int> void boo(void){}
int main(){
static_foreach<3, 2, 7>->*[&](auto i) {
foo(i);
//bar(i);
boo<i>();
};
}
但是,代码在取消注释时会抛出错误,这是一个 consteval。这能解决吗?我尝试了各种构造函数和 assigment 重载。另一个破坏游戏规则的是它不适用于浮点数和双精度,例如,在我的样条库中,还有一些函数只有在浮点数可以断言为 constexpr/consteval 类型时才能防止误用。 error: could not convert 'i' from 'std::integral_constant<int, 3>' to 'Cxpr<int>'
bar(i);
Cxpr
答:
for_loop<int,i0,ic,iE>(i){// for(int i=i0;i<iE;i+=ic), where i0,ic,iE are constexpr int
这是一个简单的语法:
for( int i:range<i0,ic,iE> ) {
// body
}
但我怀疑这是由于您选择不提及的限制(例如,想要在上下文中使用)而失败的。i
constexpr
要做到这一点,您将需要一个对用户隐藏的 lambda。
无宏版本可能如下所示:
foreach<first, increment, limit>->*[&](auto i) {
};
名称的宏注入要求将宏扩展到该位置。i
#define FOREACH( FIRST, INCREMENT, LIMIT, NAME ) \
foreach FIRST, INCREMENT, LIMIT ->* [&](auto NAME)
用户在哪里做
// Note the <> below:
FOREACH( <i0, ic, iE>, i ) {
// function body
}; //note the ; requirement
它如上所述扩展。我发现这是一个坏主意,因为如果它编译失败,你会得到一堆无意义的错误消息,用户不知道为什么会发生这种情况,可能涉及预处理器问题,也可能在生成的代码中。
诀窍是,这是一个带有重载运算符的模板值,该运算符在右侧采用可调用对象。->*
foreach<first, increment, limit>
->*
template<auto first, auto increment, auto limit>
struct static_foreach_t {
void operator->*( auto f ) const&& {
[&]<auto...Is>(std::index_sequence<Is...>) {
( f( std::integral_constant<decltype(Is), first+Is*increment>{} ), ...);
}( std::make_integer_sequence< decltype(first), (limit-first)/increment >{} );
}
};
template<auto first, auto increment, auto limit>
constexpr static_foreach_t static_foreach = {};
所以,忽略你的要求,我不喜欢:
static_foreach<1, 5, 11>->*[&](auto i) {
int array[i] = {0};
};
或者,使用宏(坏主意):
#define STATIC_FOREACH(FIRST,INCREMENT,LIMIT,NAME) \
static_foreach FIRST, INCREMENT, LIMIT ->*[&](auto NAME)
STATIC_FOREACH( <1, 5, 11>, i) {
int array[i] = {0};
std::cout << "[";
for (auto e : array)
std::cout << e;
std::cout << "]\n;
};
或者在我的首选版本中:
static_foreach<1, 5, 11>->*[&](auto i) {
int array[i] = {0};
std::cout << "[";
for (auto e : array)
std::cout << e;
std::cout << "]\n";
};
虽然可能是一堆魔术代币,但根据我的经验,将所述魔术代币隐藏在宏后面并不能使事情变得更友好。->*[&](auto i)
最终用户不会理解宏或魔术令牌,但宏意味着当宏产生错误时,最终用户将没有希望找出出了什么问题。同时,如果魔术标记产生错误,你会得到实际的编译器的帮助。
现场示例。
哦,还有一个非常大的问题。循环主体中的 A 不会执行用户期望的操作。return
return
让函数调用和 lambda 可见会让这变得不那么令人惊讶。
消除这个弱点是不可能的,因为你不能让你在 C++ 中调用的函数控制调用它的代码的返回流,除非有一些非常奇怪的协程魔术情况。
现在的类型不是 .我们可以用更多的愚蠢来解决这个问题:i
constexpr int
template<auto first, auto increment, auto limit>
struct static_foreach_t {
void operator->*( auto f ) const& {
[&]<auto...Is>(std::integer_sequence<std::size_t, Is...>) {
( f.template operator()<first+Is*increment>(), ...);
}( std::make_integer_sequence<std::size_t, (limit-first)/increment >{} );
}
};
template<auto first, auto increment, auto limit>
constexpr static_foreach_t<first, increment, limit> static_foreach = {};
#define STATIC_FOREACH(FIRST,INCREMENT,LIMIT,NAME) \
static_foreach FIRST, INCREMENT, LIMIT ->*[&]<auto NAME>()
在这里,我们没有传递 a ,而是将值作为模板参数传递给 。std::integral_constant
operator()
在非宏情况下使用稍作更改:
static_foreach<1, 5, 11>->*[&]<auto i>() {
int array[i] = {0};
std::cout << "[";
for (auto e : array)
std::cout << e;
std::cout << "]\n";
};
因为我们把非类型参数作为模板。(可以阅读或其他什么)。i
auto i
int i
对于宏情况,它看起来是相同的:
STATIC_FOREACH( <1, 5, 11>, i ) {
int array[i] = {0};
std::cout << "[";
for (auto e : array)
std::cout << e;
std::cout << "]\n";
};
对循环的非整数值的支持需要稍作调整:
template<auto first, auto increment, auto limit>
struct static_foreach_t {
void operator->*( auto f ) const& {
[&]<auto...Is>(std::integer_sequence<std::size_t, Is...>) {
( f.template operator()<first + Is*increment>(), ...);
}( std::make_integer_sequence< std::size_t, static_cast<std::size_t>((limit-first)/increment) >{} );
}
};
评论
i
bar<i>();
int
bar((int)i)
bar(i())
i
[&]<auto i>(std::integral_constant<int, i>)
->*
template operator()<Is>
评论
的范围,老实说,对于C++来说是一个令人惊讶的干净设计?