我想编写一些头文件,它的包含允许我用accessible语法编写模板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>();
}
}
正如示例所满足的,可访问语法的有用标准是:
()
、[]
、,...
),这些都无法增强循环范围和循环体的可读性等)<>
表示法 int,i0,ic,iE
来指示它们作为模板参数。 // 使用 (i)
是因为我假设我们需要一个宏来表示符号 i
相关问题在此提出:
然而,这些问题集中在功能的实现上。相反,这个问题的重点是明确实现可访问的语法。
以下是我迄今为止为实现解决方案所做的最佳尝试:
# 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)
但是,生成的语法并不令人满意。问题是:
,...
而不是 ;
()
而不是 {}
真实世界的代码示例是以下客户的代码,使用我的样条库。
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]);
}
我的软件使用基于生成源代码的运行时的自动代码转换。为了使源代码转换产生我想要的行为,索引
j
必须是 constexpr。因此,我想将其制作为模板。但是,我无法教育客户重新学习编写 for 循环的方法,从而阻止/削弱他们使用我的代码。这就引出了这个问题。
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)
可能是一堆魔法令牌,但根据我的经验,将所述魔法令牌隐藏在宏后面不会使事情变得更友好。
最终用户不会理解宏,也不会理解魔法令牌,但宏意味着当宏产生错误时,最终用户将无法弄清楚出了什么问题。同时,如果魔法令牌产生错误,您会得到实际的编译器的帮助。
实例.
哦,一个非常大的问题是
return
问题。循环体中的 return
不会执行用户期望的操作。
让函数调用和 lambda 可见可以让这变得不那么令人惊讶。
消除这个弱点是不可能的,因为你不能让你在 C++ 中调用的函数控制调用它的代码的返回流,除非有一些非常奇怪的协程魔法情况。