g++ заходит слишком далеко при компиляции

Я пытаюсь реализовать очень простую трассировку одного стека наследования в C++, используя рекурсивный шаблон:

#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();
}

Все должно быть хорошо, но когда я пытаюсь его скомпилировать, g++ игнорирует
если (TypeId (Т)== TypeId(StackTrace)) возвращение; и возвращает следующую ошибку:

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'

Он пытается вызвать C::Base::Base::Base::Base /StackTrace::Base/ class, который никогда не будет вызываться во время выполнения. Даже если я добавлю инструкцию return сразу после объявления printStackTrace, эта ошибка будет оценена. Почему область действия и функции-члены не проверяются динамически и почему компилятор игнорирует возвращаемый результат?

2 ответа

Решение

Шаблоны - это просто конструкция времени компиляции. Они просто инструктируют компилятор генерировать классы или функции, которые затем компилируются нормально (поэтому они должны быть синтаксически допустимы, за исключением некоторых особых исключений).

Вы можете обойти это, обеспечив перегрузку 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;
}

Живой пример

Независимо от того, вернетесь ли вы раньше, компилятор расширит строку

class T::Base *V;

рекурсивно. Если вы хотите, чтобы компилятор не расширял шаблоны вечно (или пока компилятор не преодолеет какое-то внутреннее ограничение, в зависимости от того, что наступит раньше:-)), вам необходимо выполнить диспетчеризацию во время компиляции. возможно, что-то подобное поможет. Измените свою функцию printStackTrace на

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

Изменить: или, как предложено в другом месте, предоставить перегрузку для типа StackTrace. Это на самом деле намного чище, чем мой непроверенный код:-)

Другие вопросы по тегам