Overloaded call is ambiguous: one-pair inline map as constructor argument - c++

I have a class––roughly similar to the one below––that takes a map as the only argument to its only constructor.
#include <iostream>
#include <map>
using namespace std;
class Dict {
public:
Dict (map<int, int> contents) {
elements = contents;
}
int getElement (int i) {
return elements[i];
}
map<int, int> elements;
};
int main() {
Dict* test0 = new Dict({{1, 2}, {3, 4}}); /* Succeeds */
Dict* test1 = new Dict({{1, 2}}); /* Fails */
}
As mentioned in the comments above, the first constructor doesn't throw an error; it's consistent with answers such as this. The ambiguous call error is as follows:
main.cpp:43:36: error: call of overloaded 'Dict()' is ambiguous
Dict* test1 = new Dict({{1, 2}}); /* Fails */
^
main.cpp:16:5: note: candidate: Dict::Dict(std::map)
Dict (map<int, int> contents) {
^
main.cpp:14:7: note: candidate: Dict::Dict(const Dict&)
class Dict {
^
main.cpp:14:7: note: candidate: Dict::Dict(Dict&&)
If the keys and values in the map are of different types (for instance, if Dict() takes a map of ints to booleans and I call new Dict({{1, true}})), this error doesn't arise and the code works as expected.
How is this single constructor ambiguous? Why is it ambiguous specifically in the case where there is one mapping between two objects of the same type? Are there any obvious work-arounds in vanilla C++?

This is caused primarily by this constructor of std::map:
template< class InputIterator >
map( InputIterator first, InputIterator last,
const Compare& comp = Compare(),
const Allocator& alloc = Allocator() );
Even if the arguments are not iterators, this constructor is enabled, thus participates overload resolution. As a result,
{1, 2} -> std::map<int, int>
{{1, 2}} -> std::map<int, int>
are both valid conversions, which means
{{1, 2}} -> Dict
{{1, 2}} -> std::map<int, int>
are both valid conversions. Therefore, the three constructors of Dict are ambiguous:
Dict(map<int, int>);
Dict(const Dict&);
Dict(Dict&&);
For the case of new Dict({{1, true}}), InputIterator cannot be deduced correctly, thus there is no ambiguity any more.
You can make Dict(map<int, int>); explicit, or use three pairs of braces suggested by Ben Voigt.
Why do three pairs of braces work?
Because in this case, for the copy/move constructor candidate, user-defined conversions are not allowed. This is explicitly stated in [over.best.ics]/4 (unrelated parts are elided by me):
However, if the target is
the first parameter of a constructor or
...
and the constructor or user-defined conversion function is a candidate by
... or
the second phase of [over.match.list] when the initializer list has exactly one element that is itself an initializer list, and the target is the first parameter of a constructor of class X, and the conversion is to X or reference to cv X,
user-defined conversion sequences are not considered.

Related

What is { } when passed to a function in CPP?

The map insert function takes {string,int} as argument. how does this work?
#include <map>
using namespace std;
int main(int argc, char *arg[])
{
map<string, int> m;
m.insert({"a", 1});
}
{"a", 1} is a braced-init-list, and when being passed to a function, copy-list-initialization (introduced in C++11) is performed.
function( { arg1, arg2, ... } ) (7)
7) in a function call expression, with braced-init-list used as an
argument and list-initialization initializes the function parameter
Given map<string, int> m; and m.insert({"a", 1});, std::map::insert expectes a std::pair<const string, int>; therefore {"a", 1} is used to initialize a temporary std::pair which is passed to .insert(). The temporary std::pair is initialized by its constructor; initializing its members first to "a" and second to 1.

Vector of pair with const member

As stated in this answer a std::vector<T> cannot contain const T, or classes with const-members. However, this is not the case when T = std::pair<const int, int>, as shown below. Why is this the case? How is std::pair special?
#include <utility>
#include <vector>
struct foo
{
const int first;
int second;
};
int main() {
std::vector<std::pair<const int, int>> V1;
V1.resize(3); // This compiles
std::vector<foo> V2;
V2.resize(3); // This gives the error listed below
}
error: use of deleted function 'foo::foo()'
note: 'foo::foo()' is implicitly deleted because the default definition would be ill-formed:
You are mixing two things here. The error that you get is due to the implicitly deleted foo() default constructor that std::vector::resize(size_type count) invokes:
If the current size is less than count,
1) additional default-inserted elements are appended
The std::pair template has a default constructor, this is why the call to V1.resize succeeds. If you provide one for foo as well, or allow its implicit generation by in class initialization, e.g.
struct foo
{
const int first = 42;
int second = 43;
};
then
std::vector<foo> V2;
V2.resize(3);
will happily compile. The operation that won't work out for both std::pair<const int, int> and foo is assignment. This won't compile:
V1[0] = std::pair<const int, int>(42, 43); // No way
V2[0] = { 42, 43 }; // Also not ok, can't assign to const data member
which doesn't have anything to do with std::vector, but with the const-qualified data members in both cases.

Why is my initializer list semi-ignored for type deduction?

Consider the following code:
#include <unordered_map>
#include <iostream>
#include <vector>
template <typename Container, typename... Containers>
inline Container get_union(
const Container& c1,
const Containers&... more_containers)
{
Container result(c1);
auto f = [&result](const Container& c) {
result.insert(std::begin(c), std::end(c));
};
[](...){}(( f(more_containers), 0)...);
return result;
}
int main()
{
std::unordered_map<int, int> m1 = { {1, 2}, {3, 4} };
decltype(m1) m2 = get_union(m1, { {5, 6}, {7, 8} } );
std::cout << "m2.size() = " << m2.size() << "\n'";
return 0;
}
When I try to build this, I get (coliru link):
g++ -std=c++14 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
main.cpp: In function 'int main()':
main.cpp:21:56: error: too many arguments to function 'Container get_union(const Container&, const Containers& ...) [with Container = std::unordered_map<int, int>; Containers = {}]'
decltype(m1) m2 = get_union(m1, { {5, 6}, {7, 8} } );
^
main.cpp:6:18: note: declared here
inline Container get_union(
^~~~~~~~~
Why is the compiler choosing the empty type-pack for Containers type, if that causes an error?
Quote from list initialization on 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.
Why is the compiler choosing the empty type-pack for Containers type
Due to how template parameter packs can be ambiguous, there are two possible ways this could be interpreted (I think). Since neither is well-formed, I'm not sure if standard even dictates which interpretation is correct.
Either the parameter pack has size 1 in which case that single type would be deduced from braced-init-list which is ill-formed.
Or, since nothing with a type was passed beyond the first argument, an empty parameter pack is the only possible allowed deduced type for Containers. Now, since the deduced function only accepts a single argument, the braced-init-list is too much. This is how the compiler chose to interpret.
Fundamentally, the problem is that you're attempting a circular deduction. You're apparently trying to deduce Containers from something whose type would be deduced from whatever Containers is - if it wasn't an empty pack.
You cannot deduce from {} when calling a function.
template <typename Container, typename... Containers>
inline Container get_union(
Container c1,
std::initializer_list<Container> more_containers)
{
for (auto&& c:more_containers)
c1.insert( std::begin(c), std::end(c) );
return c1;
}
then call like:
auto m2 = get_union(m1, { { {5, 6}, {7, 8} } } );
live example

How to delegate initializer list constructor call to a C++11 array data member? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I initialize a member array with an initializer_list?
You can construct an std::array just fine with an initializer list:
std::array<int, 3> a = {1, 2, 3}; // works fine
However, when I try to construct it from an std::initializer_list as a data member or base object in a class, it doesn't work:
#include <array>
#include <initializer_list>
template <typename T, std::size_t size, typename EnumT>
struct enum_addressable_array : public std::array<T, size>
{
typedef std::array<T, size> base_t;
typedef typename base_t::reference reference;
typedef typename base_t::const_reference const_reference;
typedef typename base_t::size_type size_type;
enum_addressable_array(std::initializer_list<T> il) : base_t{il} {}
reference operator[](EnumT n)
{
return base_t::operator[](static_cast<size_type>(n));
}
const_reference operator[](EnumT n) const
{
return base_t::operator[](static_cast<size_type>(n));
}
};
enum class E {a, b, c};
enum_addressable_array<char, 3, E> ea = {'a', 'b', 'c'};
Errors with gcc 4.6:
test.cpp: In constructor 'enum_addressable_array<T, size, EnumT>::enum_addressable_array(std::initializer_list<T>) [with T = char, unsigned int size = 3u, EnumT = E]':
test.cpp:26:55: instantiated from here
test.cpp:12:68: error: no matching function for call to 'std::array<char, 3u>::array(<brace-enclosed initializer list>)'
test.cpp:12:68: note: candidates are:
include/c++/4.6.1/array:60:12: note: std::array<char, 3u>::array()
include/c++/4.6.1/array:60:12: note: candidate expects 0 arguments, 1 provided
include/c++/4.6.1/array:60:12: note: constexpr std::array<char, 3u>::array(const std::array<char, 3u>&)
include/c++/4.6.1/array:60:12: note: no known conversion for argument 1 from 'std::initializer_list<char>' to 'const std::array<char, 3u>&'
include/c++/4.6.1/array:60:12: note: constexpr std::array<char, 3u>::array(std::array<char, 3u>&&)
include/c++/4.6.1/array:60:12: note: no known conversion for argument 1 from 'std::initializer_list<char>' to 'std::array<char, 3u>&&'
How can I get it to work so that my wrapper class can be initialized with an initializer-list, as such:
enum_addressable_array<char, 3, E> ea = {'a', 'b', 'c'};
An std::array<> has no constructor that takes an std::initializer_list<> (initializer list constructor) and there is no special language support for what it may mean to pass a std::initializer_list<> to a class' constructors such that that may work. So that fails.
For it to work, your derived class needs to catch all elements and then forward them, a constructor template:
template<typename ...E>
enum_addressable_array(E&&...e) : base_t{{std::forward<E>(e)...}} {}
Note that you need {{...}} in this case because brace elision (omitting braces like in your case) does not work at that place. It's only allowed in declarations of the form T t = { ... }. Because an std::array<> consists of a struct embedding a raw array, that will need two level of braces. Unfortunately, I believe that the exact aggregate structure of std::array<> is unspecified, so you will need to hope that it works on most implementations.
Since a std::array is a structure that contains an aggregate (it is not an aggregate itself, and does not have a constructor that takes a std::initializer_list), you can initialize the underlying aggregate inside the structure with an initializer list using a double-braces syntax like so:
std::array<int, 4> my_array = {{1, 2, 3, 4}};
Note that this is not using std::initializer_list ... this is simply using a C++ initializer list to initialize the publicly accessible array member of std::array.
An std::array does not have a constructor that takes an std::initializer_list. It's a good thing, because initializer lists can be bigger than the fixed-size of the array.
You can initialize it by testing that the initializer list is not larger than the size of the array and then copying the elements of initializer list to the elems member of std::array with std::copy.

How to construct std::array object with initializer list? [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How do I initialize a member array with an initializer_list?
You can construct an std::array just fine with an initializer list:
std::array<int, 3> a = {1, 2, 3}; // works fine
However, when I try to construct it from an std::initializer_list as a data member or base object in a class, it doesn't work:
#include <array>
#include <initializer_list>
template <typename T, std::size_t size, typename EnumT>
struct enum_addressable_array : public std::array<T, size>
{
typedef std::array<T, size> base_t;
typedef typename base_t::reference reference;
typedef typename base_t::const_reference const_reference;
typedef typename base_t::size_type size_type;
enum_addressable_array(std::initializer_list<T> il) : base_t{il} {}
reference operator[](EnumT n)
{
return base_t::operator[](static_cast<size_type>(n));
}
const_reference operator[](EnumT n) const
{
return base_t::operator[](static_cast<size_type>(n));
}
};
enum class E {a, b, c};
enum_addressable_array<char, 3, E> ea = {'a', 'b', 'c'};
Errors with gcc 4.6:
test.cpp: In constructor 'enum_addressable_array<T, size, EnumT>::enum_addressable_array(std::initializer_list<T>) [with T = char, unsigned int size = 3u, EnumT = E]':
test.cpp:26:55: instantiated from here
test.cpp:12:68: error: no matching function for call to 'std::array<char, 3u>::array(<brace-enclosed initializer list>)'
test.cpp:12:68: note: candidates are:
include/c++/4.6.1/array:60:12: note: std::array<char, 3u>::array()
include/c++/4.6.1/array:60:12: note: candidate expects 0 arguments, 1 provided
include/c++/4.6.1/array:60:12: note: constexpr std::array<char, 3u>::array(const std::array<char, 3u>&)
include/c++/4.6.1/array:60:12: note: no known conversion for argument 1 from 'std::initializer_list<char>' to 'const std::array<char, 3u>&'
include/c++/4.6.1/array:60:12: note: constexpr std::array<char, 3u>::array(std::array<char, 3u>&&)
include/c++/4.6.1/array:60:12: note: no known conversion for argument 1 from 'std::initializer_list<char>' to 'std::array<char, 3u>&&'
How can I get it to work so that my wrapper class can be initialized with an initializer-list, as such:
enum_addressable_array<char, 3, E> ea = {'a', 'b', 'c'};
An std::array<> has no constructor that takes an std::initializer_list<> (initializer list constructor) and there is no special language support for what it may mean to pass a std::initializer_list<> to a class' constructors such that that may work. So that fails.
For it to work, your derived class needs to catch all elements and then forward them, a constructor template:
template<typename ...E>
enum_addressable_array(E&&...e) : base_t{{std::forward<E>(e)...}} {}
Note that you need {{...}} in this case because brace elision (omitting braces like in your case) does not work at that place. It's only allowed in declarations of the form T t = { ... }. Because an std::array<> consists of a struct embedding a raw array, that will need two level of braces. Unfortunately, I believe that the exact aggregate structure of std::array<> is unspecified, so you will need to hope that it works on most implementations.
Since a std::array is a structure that contains an aggregate (it is not an aggregate itself, and does not have a constructor that takes a std::initializer_list), you can initialize the underlying aggregate inside the structure with an initializer list using a double-braces syntax like so:
std::array<int, 4> my_array = {{1, 2, 3, 4}};
Note that this is not using std::initializer_list ... this is simply using a C++ initializer list to initialize the publicly accessible array member of std::array.
An std::array does not have a constructor that takes an std::initializer_list. It's a good thing, because initializer lists can be bigger than the fixed-size of the array.
You can initialize it by testing that the initializer list is not larger than the size of the array and then copying the elements of initializer list to the elems member of std::array with std::copy.