C как принять список пар произвольной длины во время компиляции?

#c #templates

#c #шаблоны

Вопрос:

Я хочу создать карту только для чтения во время компиляции и надеялся подкрепить ее a std::array или std::tuple где каждый элемент является a std::pair . Для простоты использования я хотел бы избежать необходимости комментировать каждую запись при построении, и я бы хотел, чтобы она выводила количество элементов на карте, т.е.:

 constexpr MyMap<int, std::string_view> my_map{
  {1, "value1"},
  {2, "value2"},
};
 

Я перепробовал несколько стратегий для этого, но, похоже, я застрял в создании ctor или функции, которые могут принимать произвольное количество элементов, а также сообщать компилятору, что все передаваемые связанные записи (например, {1, "value1"} ) являются парой, иначе это невозможновыведите тип.

Например:

 template <typename Key, typename Mapped, typename... Args>
constexpr auto make_map(std::pair<Key, Mapped>amp;amp; first, Argsamp;amp;... args) {
  if constexpr (sizeof...(Args) == 0) {
    return std::tuple{std::forward<decltype(first)>(first)};
  }
  return std::tuple_cat(
    std::tuple{std::forward<decltype(first)>(first)},
    make_map(std::forward<Args>(args)...)
  );
}
 

Кажется, я мог бы создать макрос, который быстро позволил бы мне создавать версии функции, скажем, для всех аргументов, вплоть до разумного числа (скажем, 10-15), но это выглядит уродливее и хуже.

Есть ли способ сделать то, что я хочу, или мне нужно прибегнуть к макросам или заставить пользователей комментировать каждую запись std::pair ?

Комментарии:

1.Я не могу уловить суть! Почему бы просто не использовать массив старого стиля, например struct X { int i; std::string_view s; }; X x[] { {1, "value1"}, {2, "value2"}, }; . Вы хотите, чтобы он был отсортирован во время компиляции или в чем необходимость map ? Просто сохранить значения можно сделать, как показано на рисунке.

Ответ №1:

Если я правильно понимаю, размер карты известен и фиксирован? Если да, то почему бы не использовать обычный конструктор массива в стиле c? К сожалению, нет способа заставить компилятор определить тип списков прямой инициализации (например: вывести {1, "value"} на std::pair<int, std::string_view> ) Итак, вы должны указать тип, чтобы вычет работал.

 #include <array>
#include <string_view>
#include <utility>

template <typename K, typename V, size_t N>
class MyMap {
 public:
  using value_type = std::pair<K, V>;

  constexpr explicit MyMap(value_type(amp;amp;init)[N])
      : data_(std::to_array(std::forward<value_type[N]>(init))) {}

  const std::array<value_type, N> data_;
};

template <typename K, typename V, size_t N>
constexpr MyMap<K, V, N> MakeMyMap(
    typename MyMap<K, V, N>::value_type(amp;amp;init)[N]) {
  return MyMap{std::forward<typename MyMap<K, V, N>::value_type[N]>(init)};
}

int main(int argc, char* argv[]) {
  constexpr std::string_view value_1 = "value1";
  constexpr std::string_view value_2 = "value2";

  constexpr auto my_map = MakeMyMap<int, std::string_view>({
      {1, value_1},
      {2, value_2},
  });

  static_assert(my_map.data_.at(0) == std::make_pair(1, value_1));
  static_assert(my_map.data_.at(1) == std::make_pair(2, value_2));

  return EXIT_SUCCESS;
}
 

Примечание: это c 20 только из-за std::to_array (https://en.cppreference.com/w/cpp/container/array/to_array ). Но это можно легко реализовать в c 17

 #include <array>
#include <cstddef>
#include <type_traits>
#include <utility>

namespace internal {

template <bool Move = false, typename T, std::size_t... I>
constexpr std::array<std::remove_cv_t<T>, sizeof...(I)> to_array_impl(T (amp;a)[sizeof...(I)], std::index_sequence<I...>) {
  if constexpr (Move) {
    return {{std::move(a[I])...}};
  } else {
    return {{a[I]...}};
  }
}

}  // namespace internal

template <typename T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T (amp;a)[N]) noexcept(
    std::is_nothrow_constructible_v<T, Tamp;>) {
  static_assert(!std::is_array_v<T>);
  static_assert(std::is_constructible_v<T, Tamp;>);
  return internal::to_array_impl(a, std::make_index_sequence<N>{});
}

template <typename T, std::size_t N>
constexpr std::array<std::remove_cv_t<T>, N> to_array(T(amp;amp;a)[N]) noexcept(
    std::is_nothrow_move_constructible_v<T>) {
  static_assert(!std::is_array_v<T>);
  static_assert(std::is_move_constructible_v<T>);
  return internal::to_array_impl<true>(a, std::make_index_sequence<N>{});
}