26. 구별 공용체
튜플은 여러 가지 형식의 값을 하나의 값으로 모아 저장할 수 있게 해 간단한 구조체처럼 동작합니다. 따라서, 단 하나의 값을 갖지만 그 값의 형식은 가질 수 있는 형식 목록 중 하나를 가지도록 Variant를 구현할 것인데 이는 std::variant<>와 같은 역할을 할 것입니다.
26.1 저장소
Variant 형식을 설계할 때 가장 중요하게 여겨야 할 점은 바로 활성화된 값으로, 저장된 값의 저장소를 어떻게 관리할 지를 고려해야 합니다. 뿐만 아니라 변이 값은 현재 활성화된 값의 형식이 뭔지 알리는 구별자도 저장해야 합니다.
만약 이를 튜플로 구현한다면 상당히 비효율 적입니다. 값 하나를 저장해야는데 저장소의 크기는 값 형식의 크기를 모두 합친 것만큼 커야합니다. 따라서 이번에는 클래스가 아닌 공용체를 사용합니다.
template<typename... Types>
class VariantStorage {
using LargestT = LargestType<Typelist<Types...>>;
alignas(Types...) unsigned char buffer[sizeof(LargestT)];
unsigned char discriminator = 0;
public:
unsigned char getDiscriminator() const { return discriminator; }
void setDiscriminator(unsigned char d) { discriminator = d; }
void* getRawBuffer() { return buffer; }
const void* getRawBuffer() const { return buffer; }
template<typename T>
T* getBufferAs() { return std::launder(reinterpret_cast<T*>(buffer)); }
template<typename T>
T const* getBufferAs() const {
return std::launder(reinterpret_cast<T const*>(buffer));
}
};
template<typename Head, typename... Tail>
union VariantStorage<Head, Tail...> {
Head head;
VariantStorage<Tail...> tail;
};
template<>
union VariantStorage<> {};
공용체를 사용하면 Types 내 어떤 형식 값도 저장가능하며, 공간도 충분하고 정렬도 보장됩니다. 하지만 공용체가 다루기 어려운데 이는 상속이 되지 않기 때문입니다. 먼저 가장 큰 공간을 구하기 위해 LargestType으로 가장 큰 타입을 통해 공간을 확보하고, getBuffer를 통해 버퍼에 대한 포인터를 얻고 명시적 형식 변환을 통해 저장소를 조작, 저장을 합니다. 이는 std::launder를 통해 인자를 수정하지 않고 돌려줄 수 있게 합니다.
26.2 설계
Variant 형식은 상속이 제공되지 않기 때문에, CRTP를 사용하여 가장 많이 파생된 형식을 사용해 공유 변이 값 저장소에 접근합니다. 또한 파라미터 꾸러미에서 Types를 통해 특정 형식 T가 있는 곳을 알아내는 FindIndexOfT 메타함수도 구현하였습니다.
template<typename List, typename T, unsigned N = 0,
bool Empty = IsEmpty<List>::value>
struct FindIndexOfT;
// recursive case:
template<typename List, typename T, unsigned N>
struct FindIndexOfT<List, T, N, false>
: public IfThenElse<std::is_same<Front<List>, T>::value,
std::integral_constant<unsigned, N>,
FindIndexOfT<PopFront<List>, T, N+1>>
{};
// basis case:
template<typename List, typename T, unsigned N>
struct FindIndexOfT<List, T, N, true>
{};
template<typename T, typename... Types>
class VariantChoice {
using Derived = Variant<Types...>;
Derived& getDerived() { return *static_cast<Derived*>(this); }
Derived const& getDerived() const {
return *static_cast<Derived const*>(this);
}
protected:
// compute the discriminator to be used for this type
constexpr static unsigned Discriminator =
FindIndexOfT<Typelist<Types...>, T>::value + 1;
public:
VariantChoice() { }
VariantChoice(T const& value); // see variantchoiceinit.hpp
VariantChoice(T&& value); // see variantchoiceinit.hpp
bool destroy(); // see variantchoicedestroy.hpp
Derived& operator= (T const& value); // see variantchoiceassign.hpp
Derived& operator= (T&& value); // see variantchoiceassign.hpp
};
...정리를 하다보니 책을 보는게 더 낫다는 판단을 하였습니다. 책에서 말하는 내용을 더 크게 압축할 것도 없고 표현식을 장황하게 설명해야하기에 정리는 이정도로만 하고 책을 더 보는 걸로 마무리 짓겠습니다.