Reputation: 1111
I have a constexpr
key-value map that roughly has this definition:
// 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.
};
To initialize a key-value map at compile time, I have a utility function that forwards the elements:
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)}...);
}
The Element
is defined as:
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;
};
To actually create a map, I can use std::make_pair
successfully:
constexpr auto cmap = create_const_map<int, int>(
std::make_pair(0, 0),
std::make_pair(13, 37),
std::make_pair(42, 9001)
);
What I want now is to eliminate the calls to std::make_pair
and use braces instead:
constexpr auto cmap = create_const_map<int, int>(
{0, 0},
{13, 37},
{42, 9001}
);
But this leads to compilation failure as the {}
are not deduced as construction for pairs:
/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) {
^
What can I do so that the {a, b}
can be used instead of std::make_pair(a, b)
?
Upvotes: 4
Views: 916
Reputation: 506877
This is not easily possible because as @Rostislav says, if the argument is a braces init list, the compiler cannot deduce a type for it.
I worked it around by manually creating a sufficiently large number of overloaded functions. In the following, the overloads are the function call operators (operator()
) and create_map
is a variable template of that function object. You pass the number of overloads to create to Overload
. My C++ is rough, so perhaps this is not ideal, but it seems to work.
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>
{ };
To apply the above to your case, you can derive from Overload
and provide an exec
function.
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"});
}
Upvotes: 3
Reputation: 3977
It seems to me that it's not possible to do what you need. The compiler has to deduce the types for the Entries
parameter pack. However, the braced initializers that you want to pass in have no type. From cppreference, Notes section:
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.
That's why gcc (according to cpp.sh) says that Entries = {}
:
63:1: error: too many arguments to function 'constexpr Map create_const_map(Entries&& ...) [with K = int; V = int; Entries = {}]'
I guess if you want a more concise way to initialize your Map, you'd need to make a short-hand notation for the std::make_pair
.
Upvotes: 2