Type Casting

강보석·2024년 3월 24일

이번에는 타입 캐스팅에 대해 알아볼 것입니다. C++에서는 보통 타입 캐스팅을 할 때 static_cast, dynamic_cast를 이용합니다.
static_cast는 빠른 대신에 변환해도 되는지의 여부를 제대로 확인해주지 않아 메모리 오염을 일으킬 수 있습니다.
그에 빈해 dynamic_cast는 변환해도 되는지 여부를 잘 확인해주지만 속도는 느린 편입니다.
그래서 template을 이용하여 직접 type casting을 만들어볼 것입니다.

순서

  1. 여러 타입을 리스트처럼 보관할 수 있는 배열 만들기
  2. 해당 배열의 길이를 구하는 구조체 만들기
  3. 인자로 주어진 인덱스에 해당하는 배열의 타입을 반환하는 클래스 만들기
  4. 반대로 인자로 주어진 타입이 있는 인덱스 반환하는 클래스 만들기
  5. 두 개의 타입을 받아서 변환할 수 있는지를 반환해주는 클래스 만들기
  6. 앞의 기능들을 더하여 타입 리스트 안에 든 타입끼리의 변환이 가능한지의 관한 2차원 배열 만들기

구현

  1. 타입 리스트
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;
}
  1. 길이 구하는 구조체
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;
}
  1. 인덱스에 해당하는 배열의 타입을 반환하는 클래스
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;
}
  1. 주어진 타입이 있는 인덱스 반환하는 클래스
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;
}
  1. 두 개의 타입을 받아서 변환할 수 있는지를 반환해주는 클래스
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;
}
  1. 타입캐스팅
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같은 정수도 일종의 컴파일 단계에서 결정될 수 있는 형태로 만들기 위함입니다.
이러한 형태의 장점은 런타임이 아닌 컴파일 단계에서 결정되기 때문에 런타임에서는 빠르게 실행이 될 수 있다는 장점이 있습니다.

profile
안녕하세요. 컴퓨터를 공부하는 학생입니다.

0개의 댓글