Учтите следующее:
struct B { };
template<typename T>
struct D : B
{
T t;
}
void g(int i) { ... }
void g(string s) { ... }
void g(char c) { ... }
void f(B* b)
{
if (dynamic_cast<D<int>*>(b))
{
g(dynamic_cast<D<int>*>(b)->t);
}
else if (dynamic_cast<D<string>*>(b))
{
g(dynamic_cast<D<string>*>(b)->t);
}
else if (dynamic_cast<D<char>*>(b))
{
g(dynamic_cast<D<char>*>(c)->t)
}
else
throw error;
};
Здесь есть только три возможных типа T — int, string, char — но если бы список возможных типов был длиннее, скажем, n, цепочка if else потребовала бы O (n) для выполнения.
Одним из способов справиться с этим было бы как-то сохранить дополнительный код типа в D, а затем switch
на коде типа.
Система RTTI уже должна иметь такой код. Есть ли способ получить к нему доступ и включить его?
Или есть лучший способ сделать то, что я пытаюсь сделать?
C ++ 11 — это почти там.
В C ++ 03 это было невозможно, потому что единственный способ получить константу времени компиляции (которая case
требуется) прошел через систему типов. поскольку typeid
всегда возвращает один и тот же тип, он не может произвести разные альтернативы для switch
,
C ++ 11 добавляет constexpr
а также type_info::hash_code
как уникальный идентификатор типов, но не объединяет их. Ты можешь использовать typeid
в константном выражении типа имени или статически типизированных выражений, но потому что hash_code
это не constexpr
функция, которую вы не можете вызвать
Конечно, есть различные обходные пути, один из которых вы описываете, и самый общий из которых применяет посетителя к вектору типов с помощью шаблонного метапрограммирования.
Поскольку допустимо только несколько типов, вы можете решить это с помощью виртуальных функций и специализации шаблонов:
struct B
{
virtual void g() = 0;
}
template<typename T>
struct D : public B
{
T t;
};
template<>
struct D<int> : public B
{
int t;
void g() { /* do something here */ }
};
template<>
struct D<std::string> : public B
{
std::string t;
void g() { /* do something here */ }
};
template<>
struct D<char> : public B
{
char t;
void g() { /* do something here */ }
};
void f(B* b)
{
b->g();
}
Это потерпит неудачу во время компиляции, если вы предоставите неправильные типы, вместо этого или потребует проверки во время выполнения (что в C ++ довольно плохо).
Основным выбором для переключения типа во время выполнения в C ++ является виртуальная функция.
Это очень просто:
#include <string>
#include <iostream>
using namespace std;
struct Base
{
virtual void g() const = 0;
};
template< class Type > void g( Type const& );
template<> void g( int const& ) { cout << "int" << endl; }
template<> void g( string const& ) { cout << "string" << endl; }
template<> void g( char const& ) { cout << "char" << endl; }
template< class Type >
struct Derived: Base
{
Type t;
virtual void g() const override { ::g<Type>( t ); }
};
void f( Base& b ) { b.g(); }
int main()
{
Derived<int>().g();
}
Как вы можете это также эффективно, O (1) вместо глупого O (N). Кроме того, со статической (во время компиляции) проверкой типов вместо динамической (во время выполнения) проверки типов, что экономит довольно раздражающее количество тестов. Что еще я могу сказать? Действительно, забудьте про код типа и перечисления и тому подобное. Помните, что Бертран Мейер выбрал не поддерживается перечисляет в Eiffel, по этой причине, что люди склонны злоупотреблять ими для кодов типов. Используйте виртуальные функции.
Эй, виртуальные функции!
Они действительно полезны, когда в противном случае вы хотите динамическую отправку по типу.
Поэтому я рекомендую использовать для этого виртуальные функции. 🙂
РЕДАКТИРОВАТЬ: шаблонизированный ::g
во избежание возможных неясностей в реальном коде.