Often times, the easiest way to debug complex template code is to intentionally fail the compilation, with a minimal context, so you can read the compiler's output on what the types and values are. Here's a simple example of a function I used to use:
template <typename T, typename ...Ts>
void show_types()
{ static_assert((T*)nullptr, "Type log"); }
int main()
{
show_types<int, float, bool>();
}
When trying to compile this, using something like `g++ show-type.cpp`, you'll get this output: show-type.cpp: In instantiation of ‘void show_types() [with T = int; Ts = {float, bool}]’:
show-type.cpp:7:32: required from here
show-type.cpp:3:3: error: static assertion failed: Type log
{ static_assert((T*)nullptr, "Type log"); }
The same can be done for non-type template args, of course.