Compile-Time Lookup-Table with initializer_list - c++

Suppose you have some hash values and want to map them to their respective strings at compile time.
Ideally, I'd love to be able to write something along the lines of:
constexpr std::map<int, std::string> map = { {1, "1"}, {2 ,"2"} };
Unfortunately, this is neither possible in C++17 nor C++2a. Nevertheless,
I tried emulating this with std::array, but can't get the size of the initializer list at compile time to actually set the type of the array correctly without explicitly specifying the size.
Here is my mockup:
template<typename T0, typename T1>
struct cxpair
{
using first_type = T0;
using second_type = T1;
// interestingly, we can't just = default for some reason...
constexpr cxpair()
: first(), second()
{ }
constexpr cxpair(first_type&& first, second_type&& second)
: first(first), second(second)
{ }
// std::pair doesn't have these as constexpr
constexpr cxpair& operator=(cxpair<T0, T1>&& other)
{ first = other.first; second = other.second; return *this; }
constexpr cxpair& operator=(const cxpair<T0, T1>& other)
{ first = other.first; second = other.second; return *this; }
T0 first;
T1 second;
};
template<typename Key, typename Value, std::size_t Size = 2>
struct map
{
using key_type = Key;
using mapped_type = Value;
using value_type = cxpair<Key, Value>;
constexpr map(std::initializer_list<value_type> list)
: map(list.begin(), list.end())
{ }
template<typename Itr>
constexpr map(Itr begin, const Itr &end)
{
std::size_t size = 0;
while (begin != end) {
if (size >= Size) {
throw std::range_error("Index past end of internal data size");
} else {
auto& v = data[size++];
v = std::move(*begin);
}
++begin;
}
}
// ... useful utility methods omitted
private:
std::array<value_type, Size> data;
// for the utilities, it makes sense to also have a size member, omitted for brevity
};
Now, if you just do it with plain std::array things work out of the box:
constexpr std::array<cxpair<int, std::string_view>, 2> mapp = {{ {1, "1"}, {2, "2"} }};
// even with plain pair
constexpr std::array<std::pair<int, std::string_view>, 2> mapp = {{ {1, "1"}, {2, "2"} }};
Unfortunately, we have to explicitly give the size of the array as second template argument. This is exactly what I want to avoid.
For this, I tried building the map you see up there.
With this buddy we can write stuff such as:
constexpr map<int, std::string_view> mapq = { {1, "1"} };
constexpr map<int, std::string_view> mapq = { {1, "1"}, {2, "2"} };
Unfortunately, as soon as we exceed the magic Size constant in the map, we get an error, so we need to give the size explicitly:
//// I want this to work without additional shenanigans:
//constexpr map<int, std::string_view> mapq = { {1, "1"}, {2, "2"}, {3, "3"} };
constexpr map<int, std::string_view, 3> mapq = { {1, "1"}, {2, "2"}, {3, "3"} };
Sure, as soon as you throw in the constexpr scope, you get a compile error and could just tweak the magic constant explicitly. However, this is an implementation detail I'd like to hide. The user should not need to deal with these low-level details, this is stuff the compiler should infer.
Unfortunately, I don't see a solution with the exact syntax map = { ... }. I don't even see light for things like constexpr auto map = make_map({ ... });. Besides, this is a different API from the runtime-stuff, which I'd like to avoid to increase ease of use.
So, is it somehow possible to infer this size parameter from an initializer list at compile time?

std::array has a deduction guide:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
which lets you write:
// ok, a is array<int, 4>
constexpr std::array a = {1, 2, 3, 4};
We can follow the same principle and add a deduction guide for map like:
template <typename Key, typename Value, std::size_t Size>
struct map {
constexpr map(std::initializer_list<std::pair<Key const, Value>>) { }
};
template <class T, class... U>
map(T, U...) -> map<typename T::first_type, typename T::second_type, sizeof...(U)+1>;
Which allows:
// ok, m is map<int, int, 3>
constexpr map m = {std::pair{1, 1}, std::pair{1, 2}, std::pair{2, 3}};
Unfortunately, this approach requires naming each type in the initializer list - you can't just write {1, 2} even after you wrote pair{1, 1}.
A different way of doing it is to take an rvalue array as an argument:
template <typename Key, typename Value, std::size_t Size>
struct map {
constexpr map(std::pair<Key, Value>(&&)[Size]) { }
};
Which avoids having to write a deduction guide and lets you only have to write the type on the first one, at the cost of an extra pair of braces or parens:
// ok, n is map<int, int, 4>
constexpr map n{{std::pair{1, 1}, {1, 2}, {2, 3}, {3, 4}}};
// same
constexpr map n({std::pair{1, 1}, {1, 2}, {2, 3}, {3, 4}});
Note that the array is of pair<Key, Value> and not pair<Key const, Value> - which allows writing just pair{1, 1}. Since you're writing a constexpr map anyway, this distinction probably doesn't matter.

#Barry's answer pinpointed me in the right direction. Always explicitly listing pair in the list is undesirable. Moreover, I want to be able to partially specialize the template argument list of map. Consider the following example:
// for the sake of the example, suppose this works
constexpr map n({{1, "1"}, {2, "2"}});
// -> decltype(n) == map<int, const char*, 2>
// the following won't work
constexpr map<std::size_t, const char*> m({{1, "1"}, {2, "2"}});
However, perhaps the user wants that the map contains std::size_t as key, which does not have a literal. i.e. s/he would have to define a user-defined literal just to do that.
We can resolve this by offloading the work to a make_map function, allowing us to partially specialize the map:
// deduction guide for map's array constructor
template<class Key, class Value, std::size_t Size>
map(cxpair<Key, Value>(&&)[Size]) -> map<Key, Value, Size>;
// make_map builds the map
template<typename Key, typename Value, std::size_t Size>
constexpr auto make_map(cxpair<Key, Value>(&&m)[Size]) -> map<Key, Value, Size>
{ return map<Key, Value, Size>(std::begin(m), std::end(m)); }
// allowing us to do:
constexpr auto mapr = make_map<int, std::string_view>({ {1, "1"},
{2, "2"},
{3, "3"} });

Related

polymorphism between set and multiset in c++

Is there a way using polymorphism to have a generic for set and multiset? as follows.
Note: but only for sets, (set, multiset)
template<typename T>
void foo(parent_set<T> &s) {
// do something
}
// main
set<int> s1 = {1, 2, 3, 4, 5};
foo(s1);
multiset<int> s2 = {1, 2, 2, 2, 2, 3};
foo(s2);
Well, why don't you make the whole container your template parameter?
template <class SetType>
void foo( SetType& s)
{
using T = typename SetType :: value_type;
enter code here
....
}
The restriction for the parameter to only be a set or multiset, as is usually done with templates, is enforced by the usage of the template parameter as a set. E.g. you call insert with a single parameter, and therefore you cannot pass a vector. If there is a third, unknown, container that has all the required interface, maybe it makes sense to allow it as well?

Const map and its size

I have a std::map which cannot change at runtime. Thus, I have marked it const I cannot mark it constexpr, since has a non-literal type.
Can I deduce the size of this map at compile time?
#include <map>
#include<string>
int main (){
const std::map <int, std::string> my_map {
{ 42, "foo" },
{ 3, "bar" }
};
constexpr auto items = my_map.size();
return items;
}
This does not compile with the error:
:10:20: error: constexpr variable 'items' must be initialized
by a constant expression
constexpr auto items = my_map.size();
^ ~~~~~~~~~~~~~
:10:35: note: non-constexpr function 'size' cannot be used in
a constant expression
constexpr auto items = my_map.size();
Unfortunately, you can't use std::map and std::string in constexpt context. If it's possible, consider switching to array and string_view:
int main() {
constexpr std::array my_map{
std::pair<int, std::string_view>{ 42, "foo" },
std::pair<int, std::string_view>{ 3, "bar" }
};
constexpr auto items = my_map.size();
return items;
}
And then using constexpr std algorithms
Can I deduce the size of this map at compile time?
No. Since my_map is not a compile time constant, you cannot use it at compile time.
The standard does not provide a compile time map but there should be libraries out there or you can make your own if you really need it.
It is possible if you initialize the map via a template function
template<class... Args>
std::pair<std::integral_constant<std::size_t, sizeof...(Args)>, std::map<int, std::string>>
make_map(Args&& ...args)
{
return {{}, std::map<int, std::string>({std::forward<Args>(args)...})};
}
int main() {
const auto& p = make_map(
std::make_pair( 42, std::string("foo") ),
std::make_pair( 3, std::string("bar") )
);
constexpr std::size_t size = std::decay_t<decltype(p.first)>::value;
const auto& my_map = p.second;
//or const auto my_map = std::move(p.second);
}

Using brace-init for a non-trivial multi-variable class

I am trying to make a certain template class brace-initializable, e.g.
template<typename T>
class A {
private:
std::vector<T> _data;
std::size_t _m;
std::size_t _n;
public:
Matrix(std::size_t m, std::size_t n, const T &fill); // regular (non-trivial) constructor
Matrix(std::initializer_list<T> list);
};
However, I'm having trouble coming up with the implementation. I want to be able to do:
A<int> a = {{1, 2, 3, 4}, 2, 2};
// or something similar...e.g. C++11 style brace-init
A<int> a {{1, 2, 3, 4}, 2, 2};
I've tried:
template<typename T>
Matrix<T>::Matrix(std::initializer_list<T> list)
: _data(*list.begin()),
_m(*(list.begin() + 1)),
_n(*(list.begin() + 2)) {}
But that doesn't work for me. Help!
In order to convert from an initializer_list to a vector you may copy all the elements.
STL makes this pretty nice with the begin/end iterators. Here's all the possible constructors for vector
Matrix(std::initializer_list<T> list, std::size_t m, std::size_t n)
: _data(list.begin(), list.end()) // This is what might help
, _m(m)
,_n(n)
{
}

Wide variety of unambiguous constructors for the same class

I have the following class:
class Foo
{
public:
// Constructors here
private:
std::vector<X> m_data; // X can be any (unsigned) integer type
};
I want the following code to work:
Foo f0;
Foo f1(1); // and/or f1({1})
Foo f2(1, 2); // and/or f2({1, 2})
Foo f3(1, 2, 3); // and/or f3({1, 2, 3})
Foo f4(1, 2, 3, 4); // ... and so on
std::vector<int> vec = {1, 2, 3, 4};
Foo f5(vec);
Foo f6(vec.begin(), vec.end());
std::list<std::size_t> list = {1, 2, 3, 4};
Foo f7(list);
Foo f8(list.begin(), list.end());
std::any_iterable_container container = {1, 2, 3, 4};
Foo f9(container);
Foo f10(container.begin(), container.end());
// PS: I guess I only want containers/iterators that store individual
// values and not pairs (e.g., I don't want care about std::map
// because it does not make sense anyway).
So far I have tried to combine SFINAE, constructor overloading with all possible types, variadic templates, etc. Every time I fix one constructor case, others break down. Also, the code I write becomes very complex and hard to read. However, the problem seems quite simple and I guess I am just approaching it in a wrong way. Any suggestions on how to write the constructors (ideally in C++17), while also keeping the code as simple as possible, is more than welcome.
Thank you.
The simplest way to implement f1-f4 (which seem to take a variable number of arguments of a known type T that is not a container or iterator) ist this:
template<typename... Args>
Foo(T arg, Args... args) {...}
As this constructor takes at least 1 argument, there is no ambiguity with the default constructor f0. As the first argument is of type T, there is no ambiguity with the following constructors.
If you want to treat std::vector and std::list differently than other containers, you can create a partly specialized helper template to check if an argument is an instance of a given template:
template<typename>
struct is_vector : std::false_type {};
template<typename T, typename Allocator>
struct is_vector<std::vector<T, Allocator>> : std::true_type {};
And use it like this to implement f5 and f7:
template<typename T, Constraint = typename std::enable_if<is_vector<typename std::remove_reference<T>::type>::value, void>::type>
Foo(T arg) {...}
By testing for the respective iterator types of std::vector and std::list you can implement f6 and f8 in the same way.
You can check for the presence of member functions begin() and end() to implement f9 (I suppose) like this:
template<typename T>
Foo(T arg, decltype(arg.begin())* = 0, decltype(arg.end())* = 0) {...}
However, you'll have to explicitly disable this constructor for std::vector and std::list using the helper templates you created to avoid ambiguity.
To check if an argument is some iterator to implement f10, you can use std::iterator_traits:
template<typename T, typename Constraint = typename std::iterator_traits<T>::iterator_category>
Foo(T begin, T end) {...}
Again, you'll have to explicitly disable this constructor for the iterator types of std::vector and std::list.
The idea is to define the class like this:
template <typename X>
class Foo
{
public:
Foo() { };
Foo(initializer_list<int> l) :m_data(l) { };
template<typename container>
Foo(container const & c) :m_data(c.begin(), c.end()) {};
template<typename iterator>
Foo(iterator begin, iterator end) :m_data(begin, end) { };
private:
std::vector<X> m_data;
};
Where:
Foo() is the default (non-parametric) constructor.
Foo(initializer_list<int> l) accepts a list like {1, 2, 3}.
Foo(container const & c) accepts any container that supports begin and end iterators.
Foo(iterator begin, iterator end) initializes the class with begin and end iterators.
Usage:
Foo<int> f0;
Foo<int> f1({1});
Foo<int> f2({1, 2});
Foo<int> f3({1, 2, 3});
Foo<int> f4({1, 2, 3, 4});
std::vector<int> vec = {1, 2, 3, 4};
Foo<int> f5(vec);
Foo<int> f6(vec.begin(), vec.end());
std::list<size_t> list = {1, 2, 3, 4};
Foo<size_t> f7(list);
Foo<size_t> f8(list.begin(), list.end());
set<unsigned> container = {1, 2, 3, 4};
Foo<unsigned> f9(container);
Foo<unsigned> f10(container.begin(), container.end());
Assuming the class is defines as following:
template <class T>
class Foo
{
public:
[..]
private:
std::vector<T> m_data;
}
Let's break this task into sub-tasks:
Construct from iterators
template <class Iterator>
Foo (Iterator begin, Iterator end, typename Iterator::iterator_category * = 0)
: m_data(begin, end);
We will fill in our m_data from begin and end.
The third parameter will make sure only Iterator types that declare iterator_category will match this prototype. Since this argument has a default value of 0 and is never specified, it serves a purpose only during the template deduction process. When the compiler checks if this is the right prototype, if the type Iterator::iterator_category doesn't exist, it will skip it. Since iterator_category is a must-have type for every standard iterator, it will work for them.
This c'tor will allow the following calls:
std::vector<int> vec = {1, 2, 3, 4};
Foo<int> f(vec.begin(), vec.end());
-- AND --
std::list<std::size_t> list = {1, 2, 3, 4};
Foo<int> f(list.begin(), list.end());
Construct from container
template <class Container>
Foo (const Container & container, decltype(std::begin(container))* = 0, decltype(std::end(container))* = 0)
: m_data(std::begin(container), std::end(container));
We will fill our m_data from the given container. We iterate over it using std::begin and std::end, since they are more generic than their .begin() and .end() counterparts and support more types, e.g. primitive arrays.
This c'tor will allow the following calls:
std::vector<int> vec = {1, 2, 3, 4};
Foo<int> f(vec);
-- AND --
std::list<std::size_t> list = {1, 2, 3, 4};
Foo<int> f(list);
-- AND --
std::array<int,4> arr = {1, 2, 3, 4};
Foo<int> f(arr);
-- AND --
int arr[] = {1, 2, 3, 4};
Foo<int> f(arr);
Construct from an initializer list
template <class X>
Foo (std::initializer_list<X> && list)
: m_data(std::begin(list), std::end(list));
Note: We take the list as an Rvalue-reference as it's usually the case, but we could also add a Foo (const std::initializer_list<X> & list) to support construction from Lvalues.
We fill in our m_data by iterating over the list once again. And this c'tor will support:
Foo<int> f1({1});
Foo<int> f2({1, 2});
Foo<int> f3({1, 2, 3});
Foo<int> f4({1, 2, 3, 4});
Constructor from variable number of arguments
template <class ... X>
Foo (X ... args) {
int dummy[sizeof...(args)] = { (m_data.push_back(args), 0)... };
static_cast<void>(dummy);
}
Here, filling in the data into the container is a bit trickier. We use parameter expansion to unpack and push each of the arguments. This c'tor allows us to call:
Foo<int> f1(1);
Foo<int> f2(1, 2);
Foo<int> f3(1, 2, 3);
Foo<int> f4(1, 2, 3, 4);
Entire class
The final result is quite nice:
template <class T>
class Foo
{
public:
Foo () {
std::cout << "Default" << std::endl;
}
template <class ... X>
Foo (X ... args) {
int dummy[sizeof...(args)] = { (m_data.push_back(args), 0)... };
static_cast<void>(dummy);
std::cout << "VA-args" << std::endl;
}
template <class X>
Foo (std::initializer_list<X> && list)
: m_data(std::begin(list), std::end(list)) {
std::cout << "Initializer" << std::endl;
}
template <class Container>
Foo (const Container & container, decltype(std::begin(container))* = 0, decltype(std::end(container))* = 0)
: m_data(std::begin(container), std::end(container)) {
std::cout << "Container" << std::endl;
}
template <class Iterator>
Foo (Iterator first, Iterator last, typename Iterator::iterator_category * = 0)
: m_data(first, last) {
std::cout << "Iterators" << std::endl;
}
private:
std::vector<T> m_data;
};

C++-equivalent to incompletely initialized arrays in C?

When I transform code from C to C++ I sometimes encounter language constructs that are C, but compatible with C++. Usually I want to transform the code in the least intrusive way. But I have one case where I find that very difficult:
In C you can declare an array and initializing... well... parts of it using "designators", the rest is zeroed out (Edit: I wrote "left to randomness" here, first):
int data[7] = {
[2] = 7,
[4] = 9,
};
This is not valid C++-code, though (luckily). So I will have to use a different strategy.
While I can see a non-intrusive way in C++11:
static const map<int,int> data = { {2,7}, {4,9} };
what should I do when C++11-features are not available yet?
Can I circumvent a runtime initialization?
Is there a way to initialize similar kind of mapping in a "literal" way?
What is least intrusive to the code that uses data?
Well unless the size of the array is totally insane, you can always do this
int data[7] = {
0,
0,
7, // #2
0,
9 // #4
// the rest will be 0-initialized
};
Works in compile time too
If uniform initialization is not available, the std::map<int, int> could be initialized using boost::assign::map_list_of:
#include <boost/assign/list_of.hpp>
static const std::map<int,int> data = boost::assign::map_list_of(2,7)(4,9);
Rather than using map<int, int> you can backport std:array (or a minimal equivalent) from C++11, and use a Boost.Assign-style builder facility:
#include <cstddef>
template<typename T, size_t N> struct array { T data[N]; };
template<typename T, size_t N> struct build_array: public array<T, N> {
build_array &operator()(size_t i, const T &t) {
this->data[i] = t;
return *this;
}
};
array<int, 7> data_array = build_array<int, 7>()(2, 7)(4, 9);
int (&data)[7] = data_array.data;
Why can't you do:
int data[7];
data[2] = 7;
data[4] = 9;
looks very-very similar =)
If you do not want to use boost::assign
You can create it's simple analogue:
template<class T1, class T2>
std::map<T1, T2> cre(std::map<T1, T2> & m)
{
return std::map<T1, T2>();
}
template<class T1, class T2>
std::map<T1, T2> & ass(std::map<T1, T2> & m, T1 const & p1, T2 const & p2)
{
m[p1] = p2;
return m;
}
std::map<int, int> data = ass(ass(cre(data), 2, 3), 7, 6);