Создайте определенный кортеж из переменного аргумента функции - PullRequest
3 голосов
/ 24 октября 2019

Мне нравится строить "карту", используя std::tuple, ключ std::string, значение любого типа, оно определяется следующим образом:

template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;

И у меня есть функция MakeMap, который принимает переменные аргументы, чтобы превратить аргументы в Map и вернуть его:

template<typename... Args>
Map<???> MakeMap(Args&&... args) {
    ???
}

И я бы хотел, чтобы аргументы MakeMap были в формате std::string, type1, std::string, type2, ... (ключс последующим значением), например:

auto map = MakeMap("key1", 42, "key2", "hello world"); // OK
auto map = MakeMap(1, 2);                              // expects compiling error
auto map = MakeMap("key1", 42, "key2");                // expects compiling error

Итак, как реализовать функцию MakeMap (в C ++ 11), чтобы заставить работать вышеуказанный синтаксис вызова?

Спасибо.


РЕДАКТИРОВАТЬ

Наконец-то я понял это с помощью @ Kostas, спасибо!

  1. Сначала спариваем аргументы:
template<typename... Args>
struct MapType;

template<>
struct MapType<> {
    using type = typename std::tuple<>;
};

template<typename K, typename V, typename... Args>
struct MapType<K, V, Args...> {
    using type = std::tuple<std::pair<std::string, V>, typename MapType<Args...>::type>;
};
Теперь мы получаем вложенное std::tuple, нам нужно его сгладить (следующий фрагмент кода вдохновлен этим ответом , благодаря оригинальному автору):
template<typename T, typename U>
struct FlattenHelper;

template<typename... Args, typename... Heads, typename... Tails>
struct FlattenHelper<std::tuple<Args...>, std::tuple<std::tuple<Heads...>, Tails...>> {
    using type = typename FlattenHelper<std::tuple<Args...>, std::tuple<Heads..., Tails...>>::type;
};

template<typename... Args, typename Head, typename... Tails>
struct FlattenHelper<std::tuple<Args...>, std::tuple<Head, Tails...>> {
    using type = typename FlattenHelper<std::tuple<Args..., Head>, std::tuple<Tails...>>::type;
};

template<typename... Args>
struct FlattenHelper<std::tuple<Args...>, std::tuple<>> {
    using type = std::tuple<Args...>;
};

template<typename T>
struct Flatten;

template<typename... Args>
struct Flatten<std::tuple<Args...>> {
    using type = typename FlattenHelper<std::tuple<>, std::tuple<Args...>>::type;
};
Теперь мы можем определить MakeMap следующим образом:
template<typename... Args>
using ReturnType = typename Flatten<typename MapType<Args...>::type>::type;

template<typename K, typename V, typename... Args>
ReturnType<K, V, Args...> MakeMap(K&& k, V&& v, Args&&... args) {
    // `std::forward` is omitted here
    return std::tuple_cat(std::make_tuple(std::make_pair(k, v)), MakeMap(args...));
}

std::tuple<> MakeMap() {
    return std::tuple<>();
}

Ответы [ 2 ]

4 голосов
/ 24 октября 2019

Как обычно std::index_sequence на помощь (C ++ 14, но может быть реализовано в C ++ 11):

// C++14 alias
template <typename T>
using decay_t = typename std::decay<T>::type;

template <std::size_t I, typename T>
using tuple_element_t = typename std::tuple_element<I, T>::type;

template <std::size_t...Is, typename Tuple>
Map<decay_t<tuple_element_t<2 * Is + 1, Tuple>>...>
MakeMapImpl(std::index_sequence<Is...>, Tuple&& t)
{
    return std::make_tuple(std::make_pair(std::get<2 * Is>(t),
                                          std::get<2 * Is + 1>(t))...);
}

template <typename... Args>
auto MakeMap(Args&&... args)
-> decltype(MakeMapImpl(std::make_index_sequence<sizeof...(Args) / 2>(), std::make_tuple(args...)))
{
    static_assert(sizeof...(Args) % 2 == 0, "!");

    return MakeMapImpl(std::make_index_sequence<sizeof...(Args) / 2>(), std::make_tuple(args...));
}

Демо

2 голосов
/ 24 октября 2019

Ниже приведен способ достижения этого (с небольшими изменениями).

Из-за невозможности вычитания шаблона из неявного построения объекта, к сожалению, мы должны либо указать аргументы шаблона, либо явно инициализировать аргументы.

template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;

template<typename... Args>
Map<Args...> MakeMap(std::pair<std::string,Args>... args) {
  return Map<Args...>{args...};
}

int main() {
  auto map  = MakeMap<int, std::string>({"hello", 1}, {"hey", "ho"});
  auto map2 = MakeMap(std::pair<std::string, int>        {"hello", 1},
                      std::pair<std::string, std::string>{"hey", "ho"});
}

В качестве альтернативы, что-то ближе к тому, что вы хотели (C ++ 14):

#include <cctype>
#include <utility>
#include <string>

template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;

// declaration
template<typename ...T_Args>
auto MakeMap(T_Args...);

// empty case
template<>
auto MakeMap() {
  return std::tuple<>();
}

// inductive map case
template<typename K, typename V, typename ...T_Args>
auto MakeMap(K&& k, V&& v, T_Args&& ...V_Args) {
  static_assert(std::is_convertible<K, std::string>::value, "keys should be strings");
  static_assert(sizeof...(T_Args) % 2 == 0,                 "keys must have values" );

  return std::tuple_cat( // concatenate current key-value to the rest of the map
             std::tuple<std::pair<std::string,V>(
                 std::pair<std::string, V>(std::forward<K>(k),std::forward<V>(v))),
             MakeMap<T_Args...>(std::forward<T_Args>(V_Args)...));
}

int main() {
  auto map = MakeMap("key1", 42, "key2", "hello world"); // OK                                                                                                                       
  //auto map2 = MakeMap(1, 2);                           // Error: "keys should be strings"                                                                                          
  //auto map3 = MakeMap("key1", 42, "key2");             // Error: "keys must have values"                                                                                           
}

Чтобы использовать C++11, вам нужно вычесть типы возврата вручную:

Это сделано ниже

#include <cctype>
#include <utility>
#include <string>

template<typename ...Args>
using Map = std::tuple<std::pair<std::string, Args>...>;

// deriving type of Map given arguments to MakeMap                                                                                                                                                                 
template<typename ...Args> struct Map_tp;

template<>
struct Map_tp<> { using type = typename std::tuple<>; };

template<typename K, typename V, typename ...Args>
struct Map_tp<K,V,Args...> {
  using tuple_cat_tp = decltype(&std::tuple_cat<std::tuple<std::pair<std::string,V>>,
                                               typename Map_tp<Args...>::type>);

  using type =
    typename std::result_of<tuple_cat_tp(typename std::tuple<std::pair<std::string,V>>,
                                         typename Map_tp<Args...>::type)>::type;
};

// defining MakeMap                                                                                                                                                                                                
template<typename ...T_Args>
typename Map_tp<T_Args...>::type MakeMap(T_Args...);

// base case                                                                                                                                                                                                       
template<>
typename Map_tp<>::type
MakeMap() {
  return std::tuple<>();
}

// inductive case                                                                                                                                                                                                  
template<typename K, typename V, typename ...T_Args>
typename Map_tp<K,V,T_Args...>::type
MakeMap(K&& k, V&& v, T_Args&& ...V_Args) {
  static_assert(std::is_convertible<K, std::string>::value, "keys should be strings");
  static_assert(sizeof...(T_Args) % 2 == 0, "keys must have values");

  return std::tuple_cat(
              std::tuple<std::pair<std::string,V>>(
                   std::pair<std::string, V>(std::forward<K>(k),std::forward<V>(v))),
              MakeMap<T_Args...>(std::forward<T_Args>(V_Args)...));
}

int main() {
  auto map = MakeMap("key1", 42, "key2", "hello world"); // OK                                                                                                                                                     
  //auto map2 = MakeMap(1, 2);                           // Error: "keys should be strings"                                                                                                                        
  //auto map3 = MakeMap("key1", 42, "key2");             // Error: "keys must have values"                                                                                                                         
}

Демо

...