Debugging template instantiations
When doing metaprogramming using C++ templates, is there a method that can be used, sort of like a debugger, to step through how the templates are being instantiated and complied? It seems right now, when creating a complicated network of templates, there really isn't a very good way of debugging them other than looking at the complier error messages to see how the templates are being instantiated (if there are any compiler errors), and the attempt to work backwards from the error messages if something unexpected is being generated. I'm not really sure if what I'm looking for even exists, as it would have to be something that is done at compile time, but basically it would be a method, sort of like stepping through code and examining the stack frame in gdb
at runtime, where the compiler could be stopped and the environment examined for the sequence by which a template or set of nested templates is being instantiated开发者_开发问答.
For instance, let's say I created some simple code like the following:
template<typename T, typename R = void>
struct int_return_type {};
template<typename R>
struct int_return_type<int, R>
{
typedef R type;
};
template<typename T, typename R = void>
struct float_return_type {};
template<typename R>
struct float_return_type<float, R>
{
typedef R type;
};
template<typename T>
typename int_return_type<T>::type test()
{
cout << "T type is int" << endl;
}
template<typename T>
typename float_return_type<T>::type test()
{
cout << "T type is float" << endl;
}
int main()
{
test<int>();
test<float>();
return 0;
}
I know this is relatively easy code to follow, but templates can get quite a bit more involved, especially when doing metaprogramming, recursion, etc. I understand that the complier will issue error messages that can be used to deduce how templates are being instantiated, but I'm also wondering what can be done when the actual template code is correct in a syntactic sense, but the runtime results are still incorrect. It would be nice for instance to have a method to stop the compiler and see what test
, as well as int_return_type
and float_return_type
, was being instantiated with, or what instantiations were failing.
Are the only options available right now for debugging templates with this level of granularity 1) the compiler error messages when the code is incorrect, and 2) a combination of disassemblers and debuggers to see what instantiated code was generated if the run-time results are incorrect? Or are there some other utilities out there that help with "watching" how templates are instantiated, and see/inspect what code is generated by the compiler to investigate and debug template errors?
These are pretty basic, but they have worked for me in most cases. I'm interested to see what others have to say too.
Apologies for the contrived examples.
Use sandboxes
Starting with small sandboxes to test template code as soon as it starts behaving weird or you are doing something complicated. I am pretty comfortable with templates and I still do this almost immediately. Simply, it uncovers errors faster. You have done it for us here, so I presume that this is moot.
Specify temporary types
Temporaries can obfuscate where your intentions are not met. I have seen a lot of code that does something like the below.
template<typename T>
T calc(const T &val) {
return some_other_calc(val) / 100.0;
}
Telling the compiler what type you expect will fail faster and potentially will give you a better message to deal with.
template<typename T>
T calc(const T &val) {
T val_ = some_other_calc(val);
return val_ / 100.0;
}
Use typeid
Using typeid(T).name()
to print template names in debug statements. This will give you a string that you can use to see how the compiler decided to fulfill the type.
template<typename T>
typename void test() {
std::cout << "testing type " << typeid(T).name() << std::endl;
// ...
}
Avoid unnecessary default implementations
Write templates in such a way that they don't have default implementations.
template<typename T, bool is_integral = boost::is_numeric<T>::value >
struct my_traits;
template<typename T>
struct my_traits<T, true> {
typedef uint32_t cast_type;
};
template<typename T>
void print_whole_number(T &val) {
std::cout << static_cast<my_traits<T>::cast_type>(val) << std::endl;
}
This enforces users of print_whole_number
have their own my_traits
specialization. They will get an compiler error instead of half working because you couldn't supply a good implementation for all types. The compiler error won't be immediately helpful if used in a disparate part of a code base, admittedly.
Yes, there is a template metaprogramming debugger. Templight
I love using the excellent web-based Comeau compiler for debugging. It can notice errors in terms of standard compilance where the other compilers can't...
Comeau has the big advantage of giving a lot more readable error messages than GCC or MSVC.
Also, remember to use static_assert
's everywhere where possible -- even if you're sure the answer is true.
In 2018 we have cppinsights.io. Not sure how useful it is for really complicated templates though.
精彩评论