Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using an int as a template parameter that is not known until run-time

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.

like image 487
user396404 Avatar asked Jan 19 '13 18:01

user396404


People also ask

Can we pass non-type parameters to templates?

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.

Is a template for creating at runtime?

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.

How will you restrict the template for a specific datatype?

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.

Can a template be a template parameter?

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.)


1 Answers

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.

like image 69
Yakk - Adam Nevraumont Avatar answered Nov 10 '22 20:11

Yakk - Adam Nevraumont