In some contexts, it could be useful/necessary to have a for
loop evaluated/unrolled at compile time. For example, to iterate over the elements of a tuple
- Is there a way to write compile_time_for such that it accepts a template function as its first argument?
Short answer: no.
Long answer: a template function isn't an object, is a collection of objects and you can pass to a function, as an argument, an object, non a collection of objects.
The usual solution to this type of problem is wrap the template function inside a class and pass an object of the class (or simply the type, if the function is wrapped as a static method). That is exactly the solution you have adopted in your working code.
- If question 1. is positive, is there an overhead in the first working code, due to the fact that the routine create an object of type OperatorType at every loop iteration?
Question 1 is negative.
- Are there plans to introduce a feature like a compile-time for loop in the upcoming c++20?
I don't know C++20 enough to respond this question but I suppose not passing a set of function.
Anyway, you can do a sort of compile-time for loop using std::make_index_sequence
/std::index_sequence
starting from C++14.
By example, if you accept to extract the touple value outside your myprint()
function, you can wrap it inside a lambda and write something as follows (using also C++17 template folding; in C++14 is a little more complicated)
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <typename T>
void myprint (T const & t)
{ std::cout << t << " "; }
template <std::size_t start, std::size_t ... Is, typename F, typename ... Ts>
void ctf_helper (std::index_sequence<Is...>, F f, std::tuple<Ts...> const & t)
{ (f(std::get<start + Is>(t)), ...); }
template <std::size_t start, std::size_t end, typename F, typename ... Ts>
void compile_time_for (F f, std::tuple<Ts...> const & t)
{ ctf_helper<start>(std::make_index_sequence<end-start>{}, f, t); }
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>([](auto const & v){ myprint(v); }, x);
return 0;
}
If you really want extract the tuple element (or tuples elements) inside the function, the best I can imagine is transform your first example as follows
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <std::size_t start, template <std::size_t> class OT,
std::size_t ... Is, typename... Args>
void ctf_helper (std::index_sequence<Is...> const &, Args && ... args)
{ (OT<start+Is>{}(std::forward<Args>(args)...), ...); }
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
template <std::size_t I>
struct print_tuple_i
{
template <typename ... U>
void operator() (std::tuple<U...> const & x)
{ std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0u, 3u, print_tuple_i>(x);
return 0;
}
-- EDIT --
The OP asks
Is there some advantage of using index_sequence over my first code?
I'm not an expert but this way you avoid recursion. Compilers have recursion limits, from the template point of view, that can be strict. This way you avoid they.
Also, your code does not compile if you set the template parameters
end > start
. (One can imagine a situation where you want the compiler to determine if a loop is instantiated at all)
I suppose you mean that my code does not compile if start > end
.
The bad part is that there aren't check about this problem so the compiler try to compile my code also in this case; so encounter
std::make_index_sequence<end-start>{}
where end - start
is a negative number but used by a template that expect an unsigned number. So end - start
become a very great positive number and this can cause problems.
You can avoid this problem imposing a static_assert()
inside compile_time_for()
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
void compile_time_for (Args && ... args)
{
static_assert( end >= start, "start is bigger than end");
ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...);
}
Or maybe you can use SFINAE to disable the function
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename... Args>
std::enable_if_t<(start <= end)> compile_time_for (Args && ... args)
{ ctf_helper<start, OT>(std::make_index_sequence<end-start>{},
std::forward<Args>(args)...); }
If you want, using SFINAE you can add an overloaded compile_time_for()
version to manage the end < start
case
template <std::size_t start, std::size_t end,
template <std::size_t> class OT, typename ... Args>
std::enable_if_t<(start > end)> compile_time_for (Args && ...)
{ /* manage the end < start case in some way */ }
I'll answer on the question how to fix your last code sample.
The reason why it doesn't compile is here:
template <int start, int end, template <int, typename...> class F, typename... Args>
void compile_time_for(F f, Args... args)
/\
F is a template, you can't have an object of a template class without template parameters being substituted. E.g. you can't have on object of std::vector
type, but can have object of std::vector<int>
. I suggest you to make F
functor with a template operator() :
#include <utility>
#include <tuple>
#include <string>
#include <iostream>
template <int start, int end, typename F, typename... Args>
void compile_time_for(F f, Args... args)
{
if constexpr (start < end)
{
f.template operator()<start>(std::forward<Args>(args)...);
compile_time_for<start + 1, end>(f, std::forward<Args>(args)...);
}
}
struct myprint
{
template <int I, typename... U>
void operator()(const std::tuple<U...>& x) { std::cout << std::get<I>(x) << " "; }
};
int main()
{
std::tuple<int, int, std::string> x{1, 2, "hello"};
compile_time_for<0, 3>(myprint(), x);
return 0;
}