Language & standard: C++17
What I hope to achieve: I have created a global array, say int List[someconstant], which is constexpr constructed ; as such, I can pass its entries as template parameters, or to other constexpr functions and variables.
Now, I have a for loop such as :
for(int i = 0; i < __MYCONST__ ; i++){
int j = List[i];
function<j>(/*some data declared outside the loop*/);
}
where __MYCONST__ is known at compile time (constexpr global). I know this to be true as I have already successfully passed __MYCONST__ as a template parameter elsewhere in the code. I would now like to write
constexpr int j = List[i];
or directly
function<List[i]>();
but this fails as i is considered a variable. It is not modified within the loop and the bounds are known at compile time. Thus I believe there should be a way to get that constexpr int j
.
Notes: passing List[i] as a function argument instead of template parameter is not an option.
I have successfully created a "constexpr_loop" function:
template <int i, int N, typename FunctorType>
struct basic_recursive{
basic_recursive(FunctorType fun){
fun(i);
basic_recursive<i+1,N,FunctorType> a(fun);
};
};
template <int N, typename FunctorType>
struct basic_recursive<N,N,FunctorType>{
basic_recursive(FunctorType fun){};
};
template<int i, int N, typename FunctorType>
void constexpr_for(FunctorType fun){
basic_recursive<i,N,FunctorType> a(fun);
}
It is then called as
constexpr_for([&](int i) {loop body})
This is a bit convoluted but it works as follows:
So it's a bit of a "ping-pong" between templated functions and structs, which have complementary permissions with regards to deduction/specialization.
Now, if you think the above could be adapted to handle templated "FunctorType" with a non-type parameter, that would work; in that case, the template parameter would be the loop index and, instead of doing
fun(i);
I could do
fun<i>();
in the body of the basic_recursive constructor. I have tried compiling with -std=c++20 and using templated lambdas but I can't seem to make template parameter deduction work when one of the template parameters is itself a non-type templated type.
I have searched quite a bit to solve this problem and experimented with other solutions but they didn't seem more promising. The above code at least compiles, and is almost where I want it. I'm a beginner in C++ so I may have made mistakes exposing some aspects of the problem or the attempted solution.
-- EDIT: MWE
The furthest I've gone is as follows. Instead of the above structs and functions, define:
template <int i, int N, typename FunctorType>
struct basic_recursive2{
basic_recursive2(FunctorType fun){
fun();
basic_recursive2<i+1,N,FunctorType> a(fun);
};
};
template <int N, typename FunctorType>
struct basic_recursive2<N,N,FunctorType>{
basic_recursive2(FunctorType fun){};
};
template<int i, int N, template<int> typename FunctorType>
void constexpr_for2(FunctorType<i> fun){
basic_recursive2<i,N,FunctorType<i>> a(fun);
}
In the main, we have
constexpr_for2<1,3>([&]<int i>{printf("print in temp lambda %d \n",i);});
The main difference is that the function that is supposed to capture the type FunctorType
now expects FunctorType
to be a template<int> typename
, instead of simply a typename
. It then instantiates the template by declaring fun to be a FunctorType<i>
instead of a FunctorType. The recursive structs take a simple typename
as template parameter, which stands for the instantiated FunctorType<i>
, which is not longer a templated typename but a simple typename. Finally, the struct constructor calls fun(), which already has i as a template argument, rather than fun(i). Note: this is not satisfactory yet because I want i to be passed known by the structs so that it may be changed from call to call. But this already doesn't compile:
file.cxx: error: no matching function for call to 'constexpr_for2'
constexpr_for2<1,3>([&]<int i>{printf("print in temp lambda %d \n",i);});
^~~~~~~~~~~~~~~~~~~
file.cxx: note: candidate template ignored: could not match 'FunctorType<1>' against '(lambda at file.cxx)'
void constexpr_for2(FunctorType<i> fun){
^
file.cxx: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'FunctorType'
void constexpr_for2(FunctorType<i> fun){
^
-------------- Solution
@user17732522 proposed the following solution:
[&]<int... j>(std::integer_sequence<int, j...>){
(function<List[j]> (/*some data declared outside the loop*/), ...);
}(std::make_integer_sequence<int, __MYCONST__>{});
They have not explained it further yet, but I believe this defines a templated lambda and immediately calls it on an std::make_integer_sequence which is formed at compile time as <int, 1, ..., MYCONST>. This answer taught me about parameter pack expansion, as this is the crucial bit at work. When writing (func<j>(), ...)
, this is expanded to func<1>(), func<2(), ...
. The rest seems to be that the giving the lambda the template parameter as a dummy argument allows for template parameter deduction when calling the lambda.
I then adapted this answer to work for a loop body that has several statements. I didn't want to define an external function, so I created yet another lambda, as follows:
[&]<int... j>(std::integer_sequence<int, j...>){
( [&]<int i>(){
instruction 1 depending on i;
instruction 2 depending on i;...
}.template operator()<j>(),...);
}(std::make_integer_sequence<int, __MYCONST__>{});
Basically, the innermost lambda is templated, and we use the parameter list expansion outside of it, rather than within a statement.
This was a huge boost in performance, as templating for all loop bounds (or their indices in constexpr arrays) in the routines called by this loop provided between x2.5 to x4 speedup depending on function call parameters.
If I understand your requirement correctly, you need to invoke a templated function with template arguments from the elements of a constexpr
array. This is the simplest implementation I could achieve. The trick is passing the std::array
as a template parameter in order to invoke the target function using the elements in turn with a fold expression.
#include <array>
#include <iostream>
template<size_t N>
void target_function() {
std::cout << N << std::endl;
}
template<class T, size_t N, std::array<T, N> Arr, size_t... Is>
void indirect_function(std::index_sequence<Is...>) {
(target_function<Is>(), ...);
}
int main()
{
constexpr std::array<int, 3> arr{1, 2, 3};
indirect_function<int, 3, arr>(std::make_index_sequence<arr.size()>{});
return 0;
}
0
1
2