Olzhas Olzhas - 22 days ago 7
C++ Question

How to iterate over variadic template pack during (pseudo?) runtime?

I want to create somewhat sibling of

std::fscanf()
(I know it's a C function). So, my interface is something like this:

template <charT, char_traits, ...>
std::size_t ts_scanf(is, format, opening_bracket, closing_bracket, args)


I decided to implement a C# version of reading from console, since it requires programmer to maintain only one sequence (the args part), not the arguments and format.

Here is how C# version works:

"text blah blah blah {0} {1} {0}", arg1, arg2


So, it infers types of arg1, arg2, and then reads the text in location where {N} stands into the appropriate argument.

Algorithm of what I want to do:

1.Find opening bracket

2.Try to parse an int, say N

3.if succeeded, get the Nth parameter from the pack, read into it using
is>>get<N>args
.

4.if failed,perform dumb read

5.Repeat 1 to 4 until the end of the format or until stream exhausts

So, when writing a loop I encountered a problem:

for (i = 0; i < length; i = format.find(i, opening_bracket))


I found that I need to somehow expand the parameter pack
args
, which is impossible to do at runtime (since the loop is runtime). The only solution I have in mind is recurse: when finding opening bracket, read it, trim the format string, and recurse with the trimmed string and the rest of the variadic pack.

Question: is there a solution where it would be possible to expand the variadic pack at (pseudo) runtime?

Answer
template<class=void,  std::size_t...Is >
auto indexer( std::index_sequence<Is...> ){
  return [](auto&&f)->decltype(auto){
    return decltype(f)(f)( std::integral_constant<std::size_t, Is>{}... );
  };
}
template<std::size_t N>
auto indexer(){
  return indexer(std::make_index_sequence<N>{} );
}
template<std::size_t N>
void for_each( F&& f ) {
  indexer<N>()( [&](auto...Is){
    using discard=int[];
    (void)discard{0,(void(
      f(Is)
    ),0)...};
  });
}

indexer gives you unpacked indexes.

for_each calls f with a compile time i value for each i up to N.

That will let you iterate over integers at compile time. To map integers at runtime to compile time:

template<std::size_t N, class F>
void pick( std::size_t I, F&& f ){
  for_each<N>( [&](auto i){
    if (I==i) f(i);
  } );
}

This invokes f with a compile time version of I so long as it is less than N.

template<class...Args>
void read( std::string pattern, Args&...args ){
  auto tied=std::tie(args...);
  for (i = 0; i < length; i = format.find(i, opening_bracket))  
    pick<sizeof...(args)>( i, [&](auto i){
      std::cin>>std::get<i>(tied);
    } );
  }
}

Now there is an implicit chain of ifs written by the above; you can replace with a jump table using a different technique.

Code not compiled; design is sound, but there are probably tyops. Indexer can be found with google (I have written it on SO before). I have written it as for_each directly, but I find the single pack version too useful. Here I needed the separate pack version. Pick just uses it.

Here is a jump table version of pick:

template<std::size_t N, class F>
void pick( std::size_t I, F&& f ){
  indexer<N>()([&](auto...Is){
    using table_f=void(*)(&f);
    const table_f table[]={
      +[](F&f){ f(decltype(Is){}); }...
    };
    table[I](f);
  });
}

Bounds checking not included. This version does not require for_each, but some compilers break when asked o have a lambda with a parameter pack unexpanded inside a statement.

Comments