1

I'm trying to implement very simple single inheritance stack tracing in C++ using a recursive template:

#include <iostream>
using namespace std;
template <class C> struct MakeAlias : C{ typedef C Base; };
class StackTrace{
      public:
      static int var;
      virtual ~StackTrace() {}
      template <class T> void printStackTrace(T* c){
          if(typeid(T)==typeid(StackTrace))return; 
          cout << typeid(T).name() << "." << endl;
          class T::Base *V;
          printStackTrace(V);
     }
};
class A : public MakeAlias<StackTrace>{
};
class B : public MakeAlias<A>{
};
class C : public MakeAlias<B>{
    public:
    void hello(){
        cout << "hello from ";
        StackTrace::printStackTrace(this);
        cout << endl;
    }

};
int main(){
    C c;
    c.hello();
}

Everything should be okay, but when I try to compile it g++ ignores the
if(typeid(T)==typeid(StackTrace))return; line and returns the following error:

st.cpp: In member function `void StackTrace::printStackTrace(T*) [with T = StackTrace]':
st.cpp:13:   instantiated from `void StackTrace::printStackTrace(T*) [with T = A]'
st.cpp:13:   instantiated from `void StackTrace::printStackTrace(T*) [with T = B]'
st.cpp:13:   instantiated from `void StackTrace::printStackTrace(T*) [with T = C]'
st.cpp:24:   instantiated from here
st.cpp:12: error: no type named `Base' in `class StackTrace'
st.cpp:13: error: no type named `Base' in `class StackTrace'

It tries to call C::Base::Base::Base::Base /StackTrace::Base/ class, which will never be called during runtime. Even if I put return statement immediately after printStackTrace declaration, the same error is evaluated. Why the scope and the member functions are not checked dynamically and why the compiler ignores the return?

slaviber
  • 358
  • 2
  • 10

2 Answers2

3

Templates are a purely compile-time construct. They simply instruct the compiler to generate classes or functions, which are then compiled normally (so they must be syntactically valid, except for some special exceptions).

You can get around this by providing an overload of printStackTrace:

template <class T>
void printStackTrace(T *c)
{
  cout << typeid(T).name() << "." << endl;
  typename T::Base *V;
  printStackTrace(V);
}

void printStackTrace(StackTrace *c)
{
  cout << typeid(StackTrace).name() << "." << endl;
}

Live example

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
1

Regardless if you return early, the compiler will expand the line

class T::Base *V;

recursively. If you want to stop the compiler from expanding templates forever (or until the compiler blows some internal limit, whichever comes first :-)) you need to do the dispatching at compile time. perhaps something like this will do the trick. Change your printStackTrace function to

template <class T>
void printStackTrace(T* c) {
    printStackTraceImpl(c, std::is_same<T, StackTrace>);
}

template <class T>
void printStackTraceImpl(T* c, std::true_type) { 
      cout << typeid(T).name() << "." << endl;
      class T::Base *V;
      printStackTrace(V);
}

template <class T>
void printStackTraceImpl(T* c, std::false_type) { 
    // do nothing.
}

Edit: or as suggested elsewhere, provide an overload for type StackTrace. That's actually much cleaner than my untested code :-)

bstamour
  • 7,746
  • 1
  • 26
  • 39