Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging template instantiations

Tags:

c++

c++11

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?

like image 363
Jason Avatar asked Sep 06 '11 20:09

Jason


4 Answers

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.

like image 167
Tom Kerr Avatar answered Nov 02 '22 07:11

Tom Kerr


Yes, there is a template metaprogramming debugger. Templight

like image 25
TamaMcGlinn Avatar answered Nov 02 '22 08:11

TamaMcGlinn


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.

like image 3
Kornel Kisielewicz Avatar answered Nov 02 '22 08:11

Kornel Kisielewicz


In 2018 we have cppinsights.io. Not sure how useful it is for really complicated templates though.

like image 3
Amomum Avatar answered Nov 02 '22 07:11

Amomum