I like to build a "map" using std::tuple
, the key is std::string
, the value is any type, it's defined as following:
template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;
And I have a function MakeMap
, which accepts variadic arguments, to turn the arguments into a Map
and returns it:
template<typename... Args>
Map<???> MakeMap(Args&&... args) {
???
}
And I'd like the arguments of MakeMap
to be in std::string, type1, std::string, type2, ...
format (a key followed by a value), for example:
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
So, how to implement the function MakeMap
(in C++11), to make the above call syntax work?
Thanks.
Finally I figured it out with @Kostas's great help, thanks!
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
, we need to flatten it (the following code snippet is inspired by this answer, thanks to the original author):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
like this: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<>();
}
As usual std::index_sequence
to the rescue (C++14, but can be implemented in 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...));
}