可变参数模板构造中的隐式 std::pair 构造
implicit std::pair construction in variadic template construction
我有一个 constexpr
键值映射,大致定义如下:
// map with `pos` remaining entries
template<typename K, typename V, size_t pos>
class Map {
public:
template<class Head, class... Tail>
constexpr Map(Head head, Tail... tail)
:
value{head},
tail{tail...} {}
Element<K, V> value;
const Map<K, V, pos - 1> tail;
// members etc
};
// map end element.
template<typename K, typename V>
class Map<K, V, 0> {
public:
constexpr Map() {}
// end element specifics.
};
要在编译时初始化键值映射,我有一个转发元素的实用函数:
template<typename K, typename V, typename... Entries>
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
return Map<K, V, sizeof...(entry)>(Entry<K, V>{std::forward<Entries>(entry)}...);
}
Element
定义为:
template<class K, class V>
class Entry {
public:
constexpr Entry(const K &key, const V &value)
:
key{key},
value{value} {}
constexpr Entry(std::pair<K, V> pair)
:
key{pair.first},
value{pair.second} {}
const K key;
const V value;
};
要实际创建地图,我可以使用 std::make_pair
成功:
constexpr auto cmap = create_const_map<int, int>(
std::make_pair(0, 0),
std::make_pair(13, 37),
std::make_pair(42, 9001)
);
我现在想要的是消除对 std::make_pair
的调用并改用大括号:
constexpr auto cmap = create_const_map<int, int>(
{0, 0},
{13, 37},
{42, 9001}
);
但这会导致编译失败,因为 {}
未被推断为对的构造:
/home/jj/devel/openage/libopenage/datastructure/tests.cpp:191:24: error: no matching function for call to 'create_const_map'
constexpr auto cmap = create_const_map<int, int>(
^~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jj/devel/openage/libopenage/datastructure/constexpr_map.h:286:46: note: candidate function not viable: requires 0 arguments, but 3 were provided
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
^
我该怎么做才能使用 {a, b}
而不是 std::make_pair(a, b)
?
在我看来,你需要做的事情是不可能的。编译器必须推断出 Entries
参数包的类型。但是,您要传入的大括号初始值设定项没有类型。来自 cppreference,注释部分:
A braced-init-list is not an expression and therefore has no type, e.g. decltype({1,2})
is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a braced-init-list, so given the declaration template<class T> void f(T);
the expression f({1,2,3})
is ill-formed.
这就是为什么 gcc(根据 cpp.sh)说 Entries = {}
:
63:1: error: too many arguments to function 'constexpr Map create_const_map(Entries&& ...) [with K = int; V = int; Entries = {}]'
我想如果你想要一种更简洁的方法来初始化你的地图,你需要为 std::make_pair
做一个速记符号。
这不容易实现,因为正如@Rostislav 所说,如果参数是大括号初始化列表,则编译器无法为其推断类型。
我通过手动创建足够多的重载函数来解决这个问题。在下文中,重载是函数调用运算符 (operator()
),而 create_map
是该函数对象的变量模板。您将要创建的重载数传递给 Overload
。我的 C++ 很粗糙,所以也许这并不理想,但它似乎可以工作。
template<typename D, int N, typename ...E>
struct OverloadImpl;
template<typename D, int N, typename E, typename ...Es>
struct OverloadImpl<D, N, E, Es...> : OverloadImpl<D, N-1, E, E, Es...> {
decltype(auto) operator()(const E& e, const Es&... es) {
return static_cast<D&>(*this).exec(e, es...);
}
using OverloadImpl<D, N-1, E, E, Es...>::operator();
};
template<typename D, typename E, typename ...Es>
struct OverloadImpl<D, 0, E, Es...> {
decltype(auto) operator()() {
return static_cast<D&>(*this).exec();
}
};
template<typename D, typename E, int N>
struct Overload : OverloadImpl<D, N, E>
{ };
要将以上内容应用于您的案例,您可以从 Overload
派生并提供 exec
函数。
template<typename K, typename V, int C>
struct M {
template<typename ...T>
M(T&&...) { }
};
template<typename K, typename V>
struct Entry {
Entry(K, V) { }
};
template<typename K, typename V>
struct CreateMapImpl : Overload<CreateMapImpl<K, V>, Entry<K, V>, 10> {
template<typename ...T>
M<K, V, sizeof...(T)> exec(const T&... t) {
return { t... };
}
};
template<typename K, typename V>
CreateMapImpl<K, V> create_map{};
int main() {
create_map<int, std::string>({1, "one"}, {2, "two"}, {3, "three"});
}
我有一个 constexpr
键值映射,大致定义如下:
// map with `pos` remaining entries
template<typename K, typename V, size_t pos>
class Map {
public:
template<class Head, class... Tail>
constexpr Map(Head head, Tail... tail)
:
value{head},
tail{tail...} {}
Element<K, V> value;
const Map<K, V, pos - 1> tail;
// members etc
};
// map end element.
template<typename K, typename V>
class Map<K, V, 0> {
public:
constexpr Map() {}
// end element specifics.
};
要在编译时初始化键值映射,我有一个转发元素的实用函数:
template<typename K, typename V, typename... Entries>
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
return Map<K, V, sizeof...(entry)>(Entry<K, V>{std::forward<Entries>(entry)}...);
}
Element
定义为:
template<class K, class V>
class Entry {
public:
constexpr Entry(const K &key, const V &value)
:
key{key},
value{value} {}
constexpr Entry(std::pair<K, V> pair)
:
key{pair.first},
value{pair.second} {}
const K key;
const V value;
};
要实际创建地图,我可以使用 std::make_pair
成功:
constexpr auto cmap = create_const_map<int, int>(
std::make_pair(0, 0),
std::make_pair(13, 37),
std::make_pair(42, 9001)
);
我现在想要的是消除对 std::make_pair
的调用并改用大括号:
constexpr auto cmap = create_const_map<int, int>(
{0, 0},
{13, 37},
{42, 9001}
);
但这会导致编译失败,因为 {}
未被推断为对的构造:
/home/jj/devel/openage/libopenage/datastructure/tests.cpp:191:24: error: no matching function for call to 'create_const_map'
constexpr auto cmap = create_const_map<int, int>(
^~~~~~~~~~~~~~~~~~~~~~~~~~
/home/jj/devel/openage/libopenage/datastructure/constexpr_map.h:286:46: note: candidate function not viable: requires 0 arguments, but 3 were provided
constexpr Map<K, V, sizeof...(Entries)> create_const_map(Entries&&... entry) {
^
我该怎么做才能使用 {a, b}
而不是 std::make_pair(a, b)
?
在我看来,你需要做的事情是不可能的。编译器必须推断出 Entries
参数包的类型。但是,您要传入的大括号初始值设定项没有类型。来自 cppreference,注释部分:
A braced-init-list is not an expression and therefore has no type, e.g.
decltype({1,2})
is ill-formed. Having no type implies that template type deduction cannot deduce a type that matches a braced-init-list, so given the declarationtemplate<class T> void f(T);
the expressionf({1,2,3})
is ill-formed.
这就是为什么 gcc(根据 cpp.sh)说 Entries = {}
:
63:1: error: too many arguments to function 'constexpr Map create_const_map(Entries&& ...) [with K = int; V = int; Entries = {}]'
我想如果你想要一种更简洁的方法来初始化你的地图,你需要为 std::make_pair
做一个速记符号。
这不容易实现,因为正如@Rostislav 所说,如果参数是大括号初始化列表,则编译器无法为其推断类型。
我通过手动创建足够多的重载函数来解决这个问题。在下文中,重载是函数调用运算符 (operator()
),而 create_map
是该函数对象的变量模板。您将要创建的重载数传递给 Overload
。我的 C++ 很粗糙,所以也许这并不理想,但它似乎可以工作。
template<typename D, int N, typename ...E>
struct OverloadImpl;
template<typename D, int N, typename E, typename ...Es>
struct OverloadImpl<D, N, E, Es...> : OverloadImpl<D, N-1, E, E, Es...> {
decltype(auto) operator()(const E& e, const Es&... es) {
return static_cast<D&>(*this).exec(e, es...);
}
using OverloadImpl<D, N-1, E, E, Es...>::operator();
};
template<typename D, typename E, typename ...Es>
struct OverloadImpl<D, 0, E, Es...> {
decltype(auto) operator()() {
return static_cast<D&>(*this).exec();
}
};
template<typename D, typename E, int N>
struct Overload : OverloadImpl<D, N, E>
{ };
要将以上内容应用于您的案例,您可以从 Overload
派生并提供 exec
函数。
template<typename K, typename V, int C>
struct M {
template<typename ...T>
M(T&&...) { }
};
template<typename K, typename V>
struct Entry {
Entry(K, V) { }
};
template<typename K, typename V>
struct CreateMapImpl : Overload<CreateMapImpl<K, V>, Entry<K, V>, 10> {
template<typename ...T>
M<K, V, sizeof...(T)> exec(const T&... t) {
return { t... };
}
};
template<typename K, typename V>
CreateMapImpl<K, V> create_map{};
int main() {
create_map<int, std::string>({1, "one"}, {2, "two"}, {3, "three"});
}