이번에는 타입 캐스팅에 대해 알아볼 것입니다. C++에서는 보통 타입 캐스팅을 할 때 static_cast, dynamic_cast를 이용합니다.
static_cast는 빠른 대신에 변환해도 되는지의 여부를 제대로 확인해주지 않아 메모리 오염을 일으킬 수 있습니다.
그에 빈해 dynamic_cast는 변환해도 되는지 여부를 잘 확인해주지만 속도는 느린 편입니다.
그래서 template을 이용하여 직접 type casting을 만들어볼 것입니다.
template<typename... T>
struct TypeList;
template<typename T, typename U>
struct TypeList<T, U> {
using Head = T;
using Tail = U;
};
template<typename T, typename... U>
struct TypeList<T, U...> {
using Head = T;
using Tail = TypeList<U...>;
};
int main() {
TypeList<Knight, Mage, Archor>::Tail::Head mage;
}
template<typename T>
struct Length;
template<>
struct Length<TypeList<>> {
enum { value = 0 };
};
template<typename T, typename... U>
struct Length<TypeList<T, U...>> {
enum { value = 1 + Length<TypeList<U...>>::value };
};
int main() {
int32 len = Length<TypeList<Knight, Mage, Archor>>::value;
}
template<typename TL, int32 index>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<TypeList<Head, Tail...>, 0> {
using Result = Head;
};
template<typename Head, typename... Tail, int32 index>
struct TypeAt<TypeList<Head, Tail...>, index> {
using Result = typename TypeAt<TypeList<Tail...>, index - 1>::Result;
};
int main() {
TypeAt<TL, 0>::Result type1;
}
template<typename TL, typename T>
struct IndexOf;
template<typename... Tail, typename T>
struct IndexOf<TypeList<T, Tail...>, T> {
enum { value = 0 };
};
template<typename T>
struct IndexOf<TypeList<>, T> {
enum { value = -1 };
};
template<typename Head, typename... Tail, typename T>
struct IndexOf<TypeList<Head, Tail...>, T> {
private:
enum { temp = IndexOf<TypeList<Tail...>, T>::value };
public:
enum { value = (temp == -1) ? -1 : temp + 1 };
};
int main() {
int32 index0 = IndexOf<TL, Knight>::value;
}
template<typename From, typename To>
class Conversion {
private:
using Small = __int8;
using Big = __int32;
static Small Test(const To&) { return 0; }
static Big Test(...) { return 0; }
static From MakeFrom() { return 0; }
public:
enum {
exists = sizeof(Test(MakeFrom())) == sizeof(Small)
};
};
int main() {
bool exists1 = Conversion<Knight, Player>::exists;
}
template<int32 v>
struct Int2Type {
enum { value = v };
};
template<typename TL>
class TypeConversion {
public:
enum { length = Length<TL>::value };
TypeConversion() {
MakeTable(Int2Type<0>(), Int2Type<0>());
}
template<int32 i, int32 j>
static void MakeTable(Int2Type<i>, Int2Type<j>) {
using FromType = typename TypeAt<TL, i>::Result;
using ToType = typename TypeAt<TL, j>::Result;
if (Conversion<FromType, ToType>::exists) {
s_convert[i][j] = true;
}
else {
s_convert[i][j] = false;
}
MakeTable(Int2Type<i>(), Int2Type<j + 1>());
}
template<int32 i>
static void MakeTable(Int2Type<i>, Int2Type<length>) {
MakeTable(Int2Type<i + 1>(), Int2Type<0>());
}
template<int32 j>
static void MakeTable(Int2Type<length>, Int2Type<j>) {
}
static inline bool CanConvert(int32 from, int32 to) {
static TypeConversion conversion;
return s_convert[from][to];
}
public:
static bool s_convert[length][length];
};
template<typename TL>
bool TypeConversion<TL>::s_convert[length][length];
template<typename To, typename From>
To TypeCast(From* ptr) {
if (ptr == nullptr)
return nullptr;
using TL = typename From::TL;
if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value)) {
return static_cast<To>(ptr);
}
return nullptr;
}
template<typename To, typename From>
shared_ptr<To> TypeCast(shared_ptr<From> ptr) {
if (ptr == nullptr)
return nullptr;
using TL = typename From::TL;
if (TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value)) {
return static_pointer_cast<To>(ptr);
}
return nullptr;
}
template<typename To, typename From>
bool CanCast(From* ptr) {
if (ptr == nullptr)
return false;
using TL = typename From::TL;
return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}
template<typename To, typename From>
bool CanCast(shared_ptr<From> ptr) {
if (ptr == nullptr)
return false;
using TL = typename From::TL;
return TypeConversion<TL>::CanConvert(ptr->_typeId, IndexOf<TL, remove_pointer_t<To>>::value);
}
using TL = TypeList<class Player, class Knight, class Mage, class Archor>;
class Player {
public:
virtual ~Player() {}
using TL = TL;
int32 _typeId;
};
class Knight : public Player {
public:
Knight() {
_typeId = IndexOf<TL, Knight>::value;
}
};
class Mage : public Player {
public:
Mage() {
_typeId = IndexOf<TL, Mage>::value;
}
};
class Archor : public Player {
public:
Archor() {
_typeId = IndexOf<TL, Archor>::value;
}
};
int main() {
Player* player = new Knight();
bool canCast = CanCast<Knight*>(player); //true
player = TypeCast<Knight*>(player);
}
여기서 Int2Type을 만들어 준 이유는 앞선 명령어들은 모두 런타임이 아닌 컴파일 단계에서 결정이 됩니다. 그러한 상황에서 변수 i, j를 그냥 넣어버리면 컴파일 단계에서 결정될 수 없기 때문에 i, j같은 정수도 일종의 컴파일 단계에서 결정될 수 있는 형태로 만들기 위함입니다.
이러한 형태의 장점은 런타임이 아닌 컴파일 단계에서 결정되기 때문에 런타임에서는 빠르게 실행이 될 수 있다는 장점이 있습니다.