Not without helper code.
#define RETURNS(...) \
noexcept(noexcept(__VA_ARGS__)) \
-> decltype(__VA_ARGS__) \
{ return __VA_ARGS__; }
template<std::size_t I>
using index_t = std::integral_constant<std::size_t, I>;
template<std::size_t...Is>
constexpr auto index_over( std::index_sequence<Is...> ) noexcept(true) {
return [](auto&& f)
RETURNS( decltype(f)(f)( index_t<Is>{}... ) );
}
template<std::size_t N>
constexpr auto index_upto( index_t<N> ={} ) noexcept(true) {
return index_over( std::make_index_sequence<N>{} );
}
template<class F>
constexpr auto foreacher( F&& f ) {
return [&f](auto&&...args) noexcept( noexcept(f(args))&&... ) {
((void)(f(args)),...);
};
}
that is our plumbing.
template<int T>
void foo() {
index_upto<T>()(
foreacher([](auto I){
std::cout << I << "\n";
})
);
}
a compile time loop with values on each step.
Or we can hide the details:
template<std::size_t start, std::size_t length, std::size_t step=1, class F>
constexpr void for_each( F&& f, index_t<start> ={}, index_t<length> ={}, index_t<step> ={} ) {
index_upto<length/step>()(
foreacher([&](auto I){
f( index_t<(I*step)+start>{} );
})
);
}
then we'd get:
for_each<0, T>([](auto I) {
std::cout << I << "\n";
});
or
for_each([](auto I) {
std::cout << I << "\n";
}, index_t<0>{}, index_t<T>{});
this can be further improved with user defined literals and template variables:
template<std::size_t I>
constexpr index_t<I> index{};
template<char...cs>
constexpr auto operator""_idx() {
return index< parse_value(cs...) >;
}
where parse_value
is a constexpr
function that takes a sequence of char...
and produces an unsigned integer representation of it.