packages feed

fb-util-0.2.0.1: cpp/HsStdTuple.h

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree.
 */

#pragma once

#include <glog/logging.h>
#include <cstdarg>
#include <tuple>

#include "cpp/Destructible.h"
#include "cpp/HsStructDefines.h"

namespace hs_std_tuple::detail {

template <typename T>
T move_one(va_list& args) {
  T* l = va_arg(args, T*);
  return std::move(*l);
}

template <typename... T>
std::tuple<T...> makeTuple(va_list& args) {
  return std::tuple<T...>{move_one<T>(args)...};
}

template <typename Tuple, typename T, size_t I>
void peek_one(Tuple* t, va_list& args) {
  T* l = va_arg(args, T*);

  if constexpr (std::is_nothrow_move_constructible_v<T>) {
    // First delete the default allocated memory
    l->~T();
    // Then in-place move initialize
    new (l) T(static_cast<T&&>(std::move(std::get<I>(*t))));
  } else {
    *l = std::move(std::get<I>(*t));
  }
}

template <typename... T, std::size_t... I>
void peekVals(std::tuple<T...>* t, va_list& args, std::index_sequence<I...>) {
  (peek_one<std::tuple<T...>, T, I>(t, args), ...);
}

} // namespace hs_std_tuple::detail

#define hsc_derive_hs_std_tuple_unsafe(cxx_name...)  \
  hsc_printf(                                        \
      "deriveHsStdTupleUnsafe \"%s\" %lu %lu ",      \
      #cxx_name,                                     \
      (unsigned long)sizeof(hs_std_tuple::cxx_name), \
      (unsigned long)alignof(hs_std_tuple::cxx_name));

#define HS_STD_TUPLE_H(Name, Types) \
  namespace hs_std_tuple {          \
  using Name = HsStdTuple<Types>;   \
  }

/**
 * `__VA_ARGS__` is no good when the types listed have a comma within them.
 * For example, `std::tuple<int, HsEither<int, HsString>>` breaks as
 * `__VA_ARGS__` would give us "int", then "HsEither<int", then "HsString" as
 * the expanded type list. We need to generate code for peeking and poking
 * tuples with the types splatted out in a flat-map. The only way this seemed
 * possible was to (ab)use `va_list` to avoid needing to actually list out the
 * types. Since all the types we're sending along are pointers we don't need to
 * worry about argument promotions.
 */
#define HS_STD_TUPLE_CPP(Name)                                       \
  HS_DEFINE_MARSHALLABLE(Name, hs_std_tuple::Name)                   \
                                                                     \
  extern "C" void std_tuple_poke_##Name(                             \
      hs_std_tuple::Name* ptr, const char* fmt, ...) {               \
    va_list args;                                                    \
    va_start(args, fmt);                                             \
    new (ptr) hs_std_tuple::Name(args);                              \
    va_end(args);                                                    \
  }                                                                  \
                                                                     \
  extern "C" void std_tuple_peek_##Name(                             \
      hs_std_tuple::Name* ptr, const char* fmt, ...) {               \
    va_list args;                                                    \
    va_start(args, fmt);                                             \
    hs_std_tuple::detail::peekVals(                                  \
        ptr->getTuplePtr(),                                          \
        args,                                                        \
        std::make_index_sequence<hs_std_tuple::Name::tuple_size>{}); \
    va_end(args);                                                    \
  }

/**
 * std::tuple<> isn't default constructible, which means we can't derive
 * `Marshallable` for any `std::tuple`. Since we do really want default
 * constructibility, create this intermediate representation that introduces
 * default constructibility with DCHECK support when misused.
 */
template <typename... Ts>
HS_STRUCT HsStdTuple {
  union U {
    char mem_block[sizeof(std::tuple<Ts...>)];
    std::tuple<Ts...> tup;

    U() : mem_block() {}
    ~U() {}
  };

  U data_;

  bool has_tuple_{false};

 public:
  HsStdTuple() : data_(), has_tuple_(false) {}

  explicit HsStdTuple(std::tuple<Ts...> && t) {
    new (&data_.tup) decltype(data_.tup){std::move(t)};
    has_tuple_ = true;
  }

  HsStdTuple(const HsStdTuple&) = delete;
  HsStdTuple& operator=(const HsStdTuple&) = delete;

  HsStdTuple(HsStdTuple && other) noexcept {
    construct(std::move(other));
  }

  HsStdTuple& operator=(HsStdTuple&& other) noexcept {
    if (this != &other) {
      destruct();
      construct(std::move(other));
    }
    return *this;
  }

  explicit HsStdTuple(va_list & args) {
    new (&data_.tup) decltype(data_.tup){
        hs_std_tuple::detail::makeTuple<Ts...>(args)};
    has_tuple_ = true;
  }

  ~HsStdTuple() {
    destruct();
  }

  std::tuple<Ts...>* getTuplePtr() {
    DCHECK(has_tuple_);
    return &data_.tup;
  }

  std::tuple<Ts...> toStdTuple() && {
    return std::move(data_.tup);
  }

  template <
      typename T = HsStdTuple<Ts...>,
      typename = std::enable_if_t<std::tuple_size_v<T> == 2>>
  auto toStdPair() && {
    return std::make_pair(
        std::move(std::get<0>(data_.tup)), std::move(std::get<1>(data_.tup)));
  }

  static constexpr auto tuple_size = std::tuple_size_v<std::tuple<Ts...>>;

 private:
  void construct(HsStdTuple<Ts...> && other) noexcept {
    DCHECK(this != &other);
    has_tuple_ = other.has_tuple_;
    if (has_tuple_) {
      new (&data_.tup) decltype(data_.tup){std::move(other.data_.tup)};
    } else {
      new (&data_.mem_block) decltype(data_.mem_block){
          std::move(*other.data_.mem_block)};
    }
  }

  void destruct() {
    if (has_tuple_) {
      data_.tup.~tuple();
      has_tuple_ = false;
    }
  }
};

namespace std {

template <typename... Ts>
struct tuple_size<HsStdTuple<Ts...>> {
  static constexpr size_t value = sizeof...(Ts);
};

} // namespace std