C++공부/C++ Templates

26. 구별 공용체

아헿헿헿 2022. 7. 7. 08:13

튜플은 여러 가지 형식의 값을 하나의 값으로 모아 저장할 수 있게 해 간단한 구조체처럼 동작합니다. 따라서, 단 하나의 값을 갖지만 그 값의 형식은 가질 수 있는 형식 목록 중 하나를 가지도록 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
};

...정리를 하다보니 책을 보는게 더 낫다는 판단을 하였습니다. 책에서 말하는 내용을 더 크게 압축할 것도 없고 표현식을 장황하게 설명해야하기에 정리는 이정도로만 하고 책을 더 보는 걸로 마무리 짓겠습니다.