如何在cpp中实现可读的模板for循环语法?

问题描述 投票:0回答:1

我想编写一些头文件,它的包含允许我用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>();
   }
}

标准

正如示例所满足的,可访问语法的有用标准是:

  • 每个 for 循环的范围是固定的
  • 没有样板文件(比如创建 lambda 函数、函子类、索引序列、令人困惑的工件,如
    ()
    []
    ,...
    ),这些都无法增强循环范围和循环体的可读性等)
  • 在支撑范围内变量和函数的可访问性与经典的 for 循环相同。
  • 理想的:使用
    <>
    表示法
    int,i0,ic,iE
    来指示它们作为模板参数。 // 使用
    (i)
    是因为我假设我们需要一个宏来表示符号
    i

相关问题在此提出:

  • [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)

但是,生成的语法并不令人满意。问题是:

  • 主要:循环体仅限于一行
  • 主要:循环体必须是单个函数调用
  • semi:行尾有
    ,...
    而不是
    ;
  • semi:索引位于循环的页脚中
  • 小循环括号是
    ()
    而不是
    {}

普通 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]);
}

我的软件使用基于生成源代码的运行时的自动代码转换。为了使源代码转换产生我想要的行为,索引

j
必须是 constexpr。因此,我想将其制作为模板。但是,我无法教育客户重新学习编写 for 循环的方法,从而阻止/削弱他们使用我的代码。这就引出了这个问题。

c++ templates lambda macros variadic-templates
1个回答
1
投票
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++ 中调用的函数控制调用它的代码的返回流,除非有一些非常奇怪的协程魔法情况。

© www.soinside.com 2019 - 2024. All rights reserved.