从函数的可变参数构建特定的元组
Build a specific tuple from a function's variadic argument
我喜欢用std::tuple
建一个"map",key是std::string
,value是任意类型,定义如下:
template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;
我有一个函数 MakeMap
,它接受可变参数,将参数转换为 Map
和 returns 它:
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 的大力帮助下终于弄明白了,谢谢!
- 首先对参数进行配对:
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
,我们需要对其进行展平(以下代码片段灵感来自this answer,感谢原作者):
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<>();
}
下面是实现此目的的一种方法(有一些更改)
由于无法从隐式对象构造中推导模板,很遗憾,我们不得不指定模板参数,或者显式初始化参数。
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
你需要手动扣除 return 类型:
下面是这样做的
#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"
}
像往常一样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...));
}
我喜欢用std::tuple
建一个"map",key是std::string
,value是任意类型,定义如下:
template<typename... Args>
using Map = std::tuple<std::pair<std::string, Args>...>;
我有一个函数 MakeMap
,它接受可变参数,将参数转换为 Map
和 returns 它:
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 的大力帮助下终于弄明白了,谢谢!
- 首先对参数进行配对:
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
,我们需要对其进行展平(以下代码片段灵感来自this answer,感谢原作者):
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<>();
}
下面是实现此目的的一种方法(有一些更改)
由于无法从隐式对象构造中推导模板,很遗憾,我们不得不指定模板参数,或者显式初始化参数。
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
你需要手动扣除 return 类型:
下面是这样做的
#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"
}
像往常一样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...));
}