调试模板实例化(Debugging template instantiations)

当使用C ++模板进行元编程时,是否有可以使用的方法,像调试器一样,逐步了解如何实例化和遵守模板? 现在看来,当创建一个复杂的模板网络时,除了查看编译器错误消息之外,确实没有一个非常好的调试方法来查看模板是如何被实例化的(如果有任何编译器错误)以及如果产生意外事件,则尝试从错误消息中退回。 我不确定我正在寻找甚至是否存在,因为它必须是在编译时完成的东西,但基本上它将是一种方法,像步进代码和检查堆栈框架gdb在运行时,编译器可以停止,环境检查模板或一组嵌套模板被实例化的顺序。

例如,假设我创建了一些简单的代码,如下所示:

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; }

我知道这是相对容易的代码,但模板可能会有更多的参与,特别是当进行元编程,递归等时,我明白编译器将发出可用于推断模板被实例化的错误消息,但是我也想知道当实际的模板代码在句法意义上是正确的,但运行时结果仍然不正确时,可以做些什么。 这将是一个很好的例子,有一个方法来停止编译器,看看什么test ,以及int_return_type和float_return_type被实例化,或什么实例化失败。

现在唯一可用的调试模板具有这种粒度级别1)当代码不正确时,编译器错误消息,以及2)反汇编器和调试器的组合,以查看运行时结果是否生成了哪些实例化代码不正确的? 还是有一些其他的实用程序帮助“看”模板如何实例化,并查看/检查编译器生成的代码来调查和调试模板错误?

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?

最满意答案

这些都是很基本的,但是在大多数情况下他们都为我工作。 我有兴趣看看别人说的话。

为所设想的例子道歉。

使用沙箱

从小沙箱开始,一旦开始行为奇怪,或者您正在做一些复杂的事情,就可以测试模板代码。 我很适合模板,我几乎马上就这样做。 简单来说,它更快地发现错误。 你在这里为我们做了这件事,所以我假定这是不合适的。

指定临时类型

时态可能会混淆你的意图不符合的地方。 我看到很多代码执行了如下所示的操作。

template<typename T> T calc(const T &val) { return some_other_calc(val) / 100.0; }

告诉编译器你期望的类型会更快地失败,并且可能会给你一个更好的消息来处理。

template<typename T> T calc(const T &val) { T val_ = some_other_calc(val); return val_ / 100.0; }

使用typeid

使用typeid(T).name()在调试语句中打印模板名称。 这将为您提供一个字符串,您可以使用它来查看编译器如何确定满足该类型。

template<typename T> typename void test() { std::cout << "testing type " << typeid(T).name() << std::endl; // ... }

避免不必要的默认实现

具有默认实现的方式编写模板。

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; }

这强制print_whole_number用户有自己的my_traits专业化。 他们会收到编译器错误,而不是一半的工作,因为您无法为所有类型提供良好的实现。 诚然,如果在代码库的不同部分中使用编译器错误,则不会立即有所帮助。

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.

调试模板实例化(Debugging template instantiations)

当使用C ++模板进行元编程时,是否有可以使用的方法,像调试器一样,逐步了解如何实例化和遵守模板? 现在看来,当创建一个复杂的模板网络时,除了查看编译器错误消息之外,确实没有一个非常好的调试方法来查看模板是如何被实例化的(如果有任何编译器错误)以及如果产生意外事件,则尝试从错误消息中退回。 我不确定我正在寻找甚至是否存在,因为它必须是在编译时完成的东西,但基本上它将是一种方法,像步进代码和检查堆栈框架gdb在运行时,编译器可以停止,环境检查模板或一组嵌套模板被实例化的顺序。

例如,假设我创建了一些简单的代码,如下所示:

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; }

我知道这是相对容易的代码,但模板可能会有更多的参与,特别是当进行元编程,递归等时,我明白编译器将发出可用于推断模板被实例化的错误消息,但是我也想知道当实际的模板代码在句法意义上是正确的,但运行时结果仍然不正确时,可以做些什么。 这将是一个很好的例子,有一个方法来停止编译器,看看什么test ,以及int_return_type和float_return_type被实例化,或什么实例化失败。

现在唯一可用的调试模板具有这种粒度级别1)当代码不正确时,编译器错误消息,以及2)反汇编器和调试器的组合,以查看运行时结果是否生成了哪些实例化代码不正确的? 还是有一些其他的实用程序帮助“看”模板如何实例化,并查看/检查编译器生成的代码来调查和调试模板错误?

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?

最满意答案

这些都是很基本的,但是在大多数情况下他们都为我工作。 我有兴趣看看别人说的话。

为所设想的例子道歉。

使用沙箱

从小沙箱开始,一旦开始行为奇怪,或者您正在做一些复杂的事情,就可以测试模板代码。 我很适合模板,我几乎马上就这样做。 简单来说,它更快地发现错误。 你在这里为我们做了这件事,所以我假定这是不合适的。

指定临时类型

时态可能会混淆你的意图不符合的地方。 我看到很多代码执行了如下所示的操作。

template<typename T> T calc(const T &val) { return some_other_calc(val) / 100.0; }

告诉编译器你期望的类型会更快地失败,并且可能会给你一个更好的消息来处理。

template<typename T> T calc(const T &val) { T val_ = some_other_calc(val); return val_ / 100.0; }

使用typeid

使用typeid(T).name()在调试语句中打印模板名称。 这将为您提供一个字符串,您可以使用它来查看编译器如何确定满足该类型。

template<typename T> typename void test() { std::cout << "testing type " << typeid(T).name() << std::endl; // ... }

避免不必要的默认实现

具有默认实现的方式编写模板。

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; }

这强制print_whole_number用户有自己的my_traits专业化。 他们会收到编译器错误,而不是一半的工作,因为您无法为所有类型提供良好的实现。 诚然,如果在代码库的不同部分中使用编译器错误,则不会立即有所帮助。

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.