Я пытаюсь реализовать очень простую трассировку одного стека наследования в 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, который никогда не будет вызываться во время выполнения. Даже если я положу вернуть В операторе сразу после объявления printStackTrace вычисляется та же ошибка. Почему область действия и функции-члены не проверяются динамически и почему компилятор игнорирует вернуть?
Шаблоны — это просто конструкция времени компиляции. Они просто инструктируют компилятор генерировать классы или функции, которые затем компилируются нормально (поэтому они должны быть синтаксически допустимы, за исключением некоторые особые исключения).
Вы можете обойти это, обеспечив перегрузку 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. Это на самом деле намного чище, чем мой непроверенный код 🙂