I am trying to use an integer as a template parameter for a class. Here is a sample of the code:
template< int array_qty >
class sample_class {
public:
std::array< std::string, array_qty > sample_array;
}
If I do so something like this, it works:
sample_class< 10 > sample_class_instance;
However, let's say that I do not know the value of array_qty (the template parameter) when compiling, and will only know it during run-time. In this case, I would essentially be passing an int variable as the template argument. For the sake of demonstration, the following code does not work:
int test_var = 2;
int another_test_var = 5;
int test_array_qty = test_var * another_test_var;
sample_class< test_array_qty > sample_class_instance;
I get the following error during compile time when trying the above:
the value of ‘test_array_qty’ is not usable in a constant expression
I've tried converting test_array_qty to a const while passing it as the template parameter, but that doesn't seem to do the trick either. Is there any way to do this, or am I misusing template parameters? Perhaps they need to be known at compile time?
The goal is NOT to solve this specific approach, but rather to find a way to set the length of the array to an int variable that can be stated when instantiating the class. If there is a way to do this via a template parameter, that would be ideal.
Please note that I have to use an array for this, and NOT a vector which I may end up as a suggestion. Additionally, array_qty will always be a value between 0 and 50 - in case that makes a difference.
Template non-type arguments in C++It is also possible to use non-type arguments (basic/derived data types) i.e., in addition to the type argument T, it can also use other arguments such as strings, function names, constant expressions, and built-in data types.
Runtime templates are sometimes called "preprocessed text templates" because at compile time, the template generates code that is executed at run time. Each template is a mixture of the text as it will appear in the generated string, and fragments of program code.
There are ways to restrict the types you can use inside a template you write by using specific typedefs inside your template. This will ensure that the compilation of the template specialisation for a type that does not include that particular typedef will fail, so you can selectively support/not support certain types.
A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)
This can be done in effect. But trust me when I say you are asking the wrong question. So what follows answers your question, even thought doing it is a bad idea almost always.
What you in effect can do is create 50 different programs, one for each of the 50 possible sizes, and then conditionally jump to the one you want.
template<int n>
struct prog {
void run() {
// ...
}
};
template<int n>
struct switcher {
void run(int v) {
if(v==n)
prog<n>::run();
else
switcher<n-1>::run(v);
}
};
template<>
struct switcher<-1> {
void run(int v){
}
};
Call switcher<50>::run( value );
and if value is 0 to 50, prog<value>::run()
is invoked. Within prog::run
the template parameter is a compile time value.
Horrid hack, and odds are you would be better off using another solution, but it is what you asked for.
Here is a C++14 table-based version:
template<size_t N>
using index_t = std::integral_constant<size_t, N>; // C++14
template<size_t M>
struct magic_switch_t {
template<class F, class...Args>
using R=std::result_of_t<F(index_t<0>, Args...)>;
template<class F, class...Args>
R<F, Args...> operator()(F&& f, size_t i, Args&&...args)const{
if (i >= M)
throw i; // make a better way to return an error
return invoke(std::make_index_sequence<M>{}, std::forward<F>(f), i, std::forward<Args>(args)...);
}
private:
template<size_t...Is, class F, class...Args>
R<F, Args...> invoke(std::index_sequence<Is...>, F&&f, size_t i, Args&&...args)const {
using pF=decltype(std::addressof(f));
using call_func = R<F, Args...>(*)(pF pf, Args&&...args);
static const call_func table[M]={
[](pF pf, Args&&...args)->R<F, Args...>{
return std::forward<F>(*pf)(index_t<Is>{}, std::forward<Args>(args)...);
}...
};
return table[i](std::addressof(f), std::forward<Args>(args)...);
}
};
magic_switch_t<N>{}( f, 3, blah1, blah2, etc )
will invoke f(index_t<3>{}, blah1, blah2, etc)
.
Some C++14 compilers will choke on the variardic pack expansion containing a lambda. It isn't essential, you can do a workaround, but the workaround is ugly.
The C++14 features are all optional: you can implement it all in C++11, but again, ugly.
The f
passed basically should be a function object (either a lambda taking auto
as the first argument, or a manual one). Passing a function name directly won't work well, because the above best works when the first argument becomes a compile-time value.
You can wrap a function template with a lambda or a function object to help.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With