Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
251 views
Welcome To Ask or Share your Answers For Others

1 Answer

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...