Reason for Using Non-Type Template Parameter Instead of Regular Parameter

Reason for using non-type template parameter instead of regular parameter?

There are many applications for non-type template arguments; here are a few:

You can use non-type arguments to implement generic types representing fixed-sized arrays or matrices. For example, you might parameterize a Matrix type over its dimensions, so you could make a Matrix<4, 3> or a Matrix<2, 2>. If you then define overloaded operators for these types correctly, you can prevent accidental errors from adding or multiplying matrices of incorrect dimensions, and can make functions that explicitly communicate the expected dimensions of the matrices they accept. This prevents a huge class of runtime errors from occur by detecting the violations at compile-time.

You can use non-type arguments to implement compile-time function evaluation through template metaprogramming. For example, here's a simple template that computes factorial at compile-time:

template <unsigned n> struct Factorial {
enum {
result = n * Factorial<n - 1>::result
};
};
template <> struct Factorial<0> {
enum {
result = 1
};
};

This allows you to write code like Factorial<10>::result to obtain, at compile-time, the value of 10!. This can prevent extra code execution at runtime.

Additionally, you can use non-type arguments to implement compile-time dimensional analysis, which allows you to define types for kilograms, meters, seconds, etc. such that the compiler can ensure that you don't accidentally use kilograms where you meant meters, etc.

Hope this helps!

Why are non-type template arguments used?

They are useful when you want (or need) the compiler to make certain compile-time optimizations while keeping code well-factored. I'll briefly list a few examples:

Branch elimination

void doSomething(bool flag) {
if (flag) {
//whatever
}
}

template <bool flag>
void doSomething() {
if (flag) {
//whatever
}

}

In the above example, if you always know the value of flag at compile time when you call doSomething, then you can avoid the cost of a branch without manually creating doSomethingTrue and doSomethingFalse functions which may require you to repeat a lot of code. This trick is useful when you want to factor code between send and recv sides in network code, for example, and your code sits deep down in a performance-critical stack.

Avoid dynamic memory management

void someFunction(size_t size, float* inout) {
std::vector<float> tmp(size);
//do something with inout and tmp and then store result in inout
}

template <size_t N>
void someFunction(float *inout) {
float tmp[N]; //or std::array if you like
//do something with inout and tmp and store result in inout
}

In the above example, the second version will perform much better if it is stuck inside a critical loop.

Allow special optimizations

Consider the loop:

for (size_t i = 0; i < N; ++i) {
//some arithmetic
}

If the compiler knows that N is 4 or 8, it may be able to replace your arithmetic with equivalent SIMD instructions, or at least unroll your loop more intelligently than it could if N were a runtime argument. This sort of thing is commonplace in the GPU programming (CUDA) world.

Template meta-programming

When you do some non-trivial code generation with templates, sometimes you must iterate over things at compile time. You need template non-type arguments to be able to do this. Argument packs and std::tuple are one common case, but there are many more, and hard to exemplify here.

To answer your question of when you'd make something a template parameter if you have a choice: code that takes dynamic (runtime) arguments is always more flexible, so you should only convert something to be a template argument if you can justify it with a substantial gain in performance. On the other hand if you find yourself writing much duplicated code over a set of enumerations, you should probably try and factor with templates.

What's good of using non-type template parameters?

Thus I doubt (correct me if I'm wrong) that every different size value leads to the creation of new binary codes (the ".text"), which seems to be an overhead.

This is actually the case, and this is a common source of code bloat. You need to figure out when you want to generate different functions for each N and when you want a single function in which the compiler has less information (note that this is not just for performance, also for correctness).

Since Matt already brought a simple example, lets work on a function that takes an array by reference.:

template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] )
{
// Some complex logic, which could include:
for (std::size_t i = 0; i < N; ++i) {
// complicated stuff
}
}

The type of the argument is a reference to an array, the compiler will verify for you that the array truly has N elements (and it will deduce the type of the values in the array). This is a great improvement in type safety compared with some similar C style code:

size_t operateOnArray( T *array, size_t N)
{
// Some complex logic, which could include:
for (std::size_t i = 0; i < N; ++i) {
// complicated stuff
}
}

In particular, the user can mistakenly pass the wrong value:

int array[10];
operateOnArray(arrah, 20); // typo!!!

Where in the first case the compiler will deduce the size and it will guarantee that it is correct.

You hit the nail in the head when you mentioned that this can potentially add to the code size, and that it can add quite a lot. Imagine that the function is complex enough that it does not get inlined, and imagine that in your program you end up calling the function with all sizes from 1 to 100. The program code will contain 100 instantiations of basically the same code where the only difference is the size.

There are solutions around this, like mixing the two approaches:

size_t operateOnArray( T *array, size_t N); // Possibly private, different name...
template<typename T, size_t N>
size_t operateOnArray( T (&array)[N] ) {
operateOnArray(array, N);
}

In this case, the compiler will have one single copy of the complex code, in the C-style function, and will generate 100 versions of the template, but those are simple, simple enough that the compiler will inline the code and transform the program into the equivalent of the C-style approach with guaranteed type safety.

Can anyone tell when this is necessary and worthwhile?

It is necessary when the code inside the template requires the value as a compile time constant, for example in the code above, you cannot have a function argument that is a reference to an array of N elements where the N is only available at runtime. In other cases, like std::array<T,N> it is required to statically create an array of the proper size. No matter what, all examples shared that: the value needs to be known at compile time.

It is worthwhile, well, when it adds type safety to your program (see example above), or if it will allow stronger optimizations (a functor taking a function pointer/member function pointer as non-type argument can inline the function call).

And you should be aware that everything comes at a cost, in this case binary size. If the template is small enough that the code is likely to be inlined, don't worry, but if the code is quite complex, consider using hybrid approaches where you use a template argument where needed or if it provides a big advantage and regular arguments otherwise.

Why use a non-type parameter in C++?

Non-type template parameters are consumed at compile time, and instantiating a template with a unique value creates a new type or function. This information can then be used for "configuration" that is impossible at run time. This is somewhat horizontal to a template type - a class template parameter T achieves reusability w.r.t. different types, while non-type template parameters offer a different kind of reusability.

std::array is a good example for that. I assume its class definition has a data member that is a plain array with the size N, e.g.

template<class T, std::size_t N>
struct array {
// ...

T wrapped[N];
};

If N was a runtime value (e.g. passed to a constructor), there is no way to do such things.

Another example where such compile-time "configuration" of class templates is useful are "small vectors", i.e., containers that have a fixed buffer size, postponing dynamic memory allocation to the point where that buffer is full. Such buffer size can also only be specified with a non-type template parameter. And given that memory allocation can be a performance bottleneck, such techniques are of great importance.

When to use a template non-type argument VS. a constructor parameter?

Template non-type parameters are generally more restrictive than function/constructor parameters, but they can also do some unique things when compile time constants are needed.

Commonly they will be mainly an optimization benefit and sometimes the code can be simpler, e.g. in your MyArray case, or a std::array.

template<typename T, int N>
class MyArray {
private:
T internal_array[N]; // No extra allocation here
public:
MyArray() {}
void fill(int n) {
for (auto& x : internal_array) { // The compiler can easily optimize loops like this, as the number of loops is known.
x = n;
}
}
void print() {
for (auto x : internal_array) {
std::cout << x;
}
}
};

Another is that it is possible to have things like the return type, arguments, or other functionality, be dependent on a non-type parameter, for example see std::get<i>(my_tuple) where i is known at compile time, the return type will be the type of that tuples element, if it was say std::get(i, my_tuple) then it couldn't have different return types.

std::tuple<std::string, int> t = { "example", 42 };
std::string str = std::get<0>(t);
int x = std::get<1>(t);

I have also seen "flags" specified this way where compile time constants are later needed, or to get the compiler to optimize the specific cases better.

my_function<CASE_INSENSITIVE | NO_UNICODE>(a, b, c);

when to use template non-type classes or plain arguments in constexpr functions

The short version: Use non-type template parameter to set non-type template arguments (more general everywhere, where you need a constant expression) and normal arguments for everything else.

The thing about constexpr functions you always have to keep in mind is that they can also be called at runtime. So every normal argument is not necessarily a constant expression. Hence you cannot use it to provide a non-type template argument (as the I in std::get<I>).

Of course one could argue that when called to calculate a constexpr variable the passed arguments are always constant expressions and could be used as such also inside the function. But it would be unexpected if a constexpr function works at compile time but not anymore at runtime.

One could expect that with the new consteval keyword in C++20, one could use normal arguments to consteval functions in constant expressions, since we know that these arguments have to be constant expressions. But this does not seem to be the case: https://godbolt.org/z/guz7FQ Why this is the case I do not know. But in general I like the seperation between normal variables and non-type template arguments.

template non-type template parameters

Your base declaration does not match your specialization.

The base implementation has template <class...Args> while the specialzation wants template <int eventType, class...Args>.

You also put an extra int that does not belong there in the declaration for the specialization here:

template<int eventType,class...Args>
class Event<int eventType, bool(Args...)> : public IEvent
^^^ here

The adjusted code would look like this

#include <stdio.h>
#include <iostream>
#include <functional>
#include <vector>
using namespace std;

class IEvent
{

public:
int m_EventType;
virtual ~IEvent() {}
};

template<int eventType, class...Args>
class Event : public IEvent {};

template<int eventType,class...Args>
class Event<eventType, bool(Args...)> : public IEvent
{
public:
Event(bool(*func)(Args...)) :m_FnPtr(func)
{
m_EventType = eventType;
m_ListenersList.push_back(m_FnPtr);
}

template <typename T>
Event(T* obj, bool(T::* Func)(Args...))
{
m_EventType = eventType;
m_FnPtr = [obj, Func](Args&&... args)->bool {
return (obj->*Func)(std::forward<Args>(args)...);
};

m_ListenersList.push_back(m_FnPtr);
}

void NotifyListeners(Args&&...args) const
{
for (auto& itr : m_ListenersList)
{
(itr)(std::forward<Args>(args)...);
}

}
private:
std::function<bool(Args...)> m_FnPtr;
std::vector<std::function<bool(Args...)>> m_ListenersList;
};

class Window
{
public:
bool OnKeyUp(bool, double)
{
cout << endl << "Member Function called";
return true;
}

bool OnClicked()
{
cout << endl << "OnClicked";
return true;
}

//using KeyupListenerType = Event<"KeyUp", bool(bool, double)>;
};

int main()
{
Window w;
Event<90,bool(bool, double)> evnt(&w, &Window::OnKeyUp);
//Event<100,bool()> evnt(&w, &Window::OnClicked);
evnt.NotifyListeners(true, 6.8);
return 0;
}

C++20 non-type template parameter which is template in the prior type parameters: is not a valid template arg, because is not a variable

Edit:

The third template parameter of sillier is a non-type template argument, which can only be bound to a variable, but dumb_func is not a variable.

This explanation doesn't make sense, and in fact the code is probably fine, and the error might just be a bug as discovered and reported in this answer. The fix given below still works though.


You can make the third template parameter be a non-type template parameter of reference type.

template<typename T, typename J, silly<T, J> const & aSilly>
// ^^^^^^^
struct sillier
{
const uint32_t something;
};

Also, the member variable f in silly is declared as a function pointer returning a const void type:

const void (*f)(T,J);

So you either need to remove the const from the return type of f, or you can change the declaration of dumb_func to return a const void type:

const void dumb_func(uint32_t i, uint32_t j)
{
return;
}

There doesn't seem to be any point in a const void returning function, so I would go with the first option.


Here's a demo.



Related Topics



Leave a reply



Submit