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.
Related
How can I use variants as the key in unordered_map?
For example, I'd like to make the following code work.
using VariantType = std::variant<int, std::string, unsigned int>;
std::unordered_map<VariantType, int, $some_hash_function$> m;
How do I implement $some_hash_function$?
There is already a hash template specialization for variant:
http://en.cppreference.com/w/cpp/utility/variant/hash
The only condition is that every type in the variant must have a hash function:
The specialization std::hash<std::variant<Types...>> is enabled (see std::hash) if every specialization in std::hash<std::remove_const_t<Types>>... is enabled, and is disabled otherwise.
But all your variant types have default hashes so, for your variant types, it compiles without the third parameter because the standard hash works. However, if you had a type in your variant that did not have a hash function (or an == operator) then it would fail to compile with this error:
error: static assertion failed: hash function must be invocable with an argument of key type
So back to your question:
When the variant types have hash functions:
#include <variant>
#include <unordered_map>
#include <string>
#include <iostream>
using VariantType = std::variant<int, std::string, unsigned int>;
std::unordered_map<VariantType, int> m =
{
{1, 1},
{2u, 2},
{std::string("string"),3}
};
int main()
{
VariantType v = std::string{"string"};
std::cout << m[v];
}
You get this output:
Program returned: 0
Program stdout
3
And when not all the variant types have hash functions:
#include <variant>
#include <unordered_map>
#include <string>
#include <iostream>
class UnhashedClass {};
using VariantType = std::variant<UnhashedClass, int, std::string>;
std::unordered_map<VariantType, int> m =
{
{1, 1},
{2u, 2},
{std::string("string"),3}
};
int main()
{
VariantType v = std::string{"string"};
std::cout << m[v];
}
You get this output:
Could not execute the program
Compiler returned: 1
Compiler stderr
...
error: static assertion failed: hash function must be invocable with an argument of key type
...
You can try it yourself here:
https://godbolt.org/z/bnzcE9
I do not understand why this works fine:
std::array<double, 2> someArray = {0,1};
std::shared_ptr<MyClass> myobj = std::make_shared<MyClass>(someArray);
But this does not work:
std::shared_ptr<MyClass> myobj = std::make_shared<MyClass>({0,1});
Compiler says:
too many arguments to function ‘std::shared_ptr< _Tp> std::make_shared(_Args&& ...)
...
candidate expects 1 argument, 0 provided
Question: Can someone clarify why this happens and if there is any way I can fix the second approach without defining an extra variable?
Edit:
Example of MyClass:
#include <memory> //For std::shared_ptr
#include <array>
#include <iostream>
class MyClass{
public:
MyClass(std::array<double, 2> ){
std::cout << "hi" << std::endl;
};
};
Braced initializers {} can never be deduced to a type (in a template context). A special case is auto, where it is deduced to std::initializer_list. You always have to explictly define the type.
auto myobj = std::make_shared<MyClass>(std::array<double, 2>{0, 1});
The type of {0, 0} is context-dependent. If {0, 0} is being used to immediately construct another object of known type, then it represents a prvalue of that object type:
MyClass m({0, 0});
Here, {0, 0} refers to a prvalue of type std::array<double, 2>
On the other hand, if there are no constraints on the type, then {0, 0} refers to an initializer list of type std::initializer_list<int>:
auto vals = {0, 0};
There's no way to initialize MyClass from std::initializer_list<int>, so make_shared fails to compile:
MyClass m(vals); // Fails: can't construct MyClass from initializer list
How does this connect to std::make_shared? Because std::make_shared is a template, {0, 0} isn't being used to construct a specific type. As a result, it's treated as a std::initializer_list.
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.
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.
A comparator comp was defined as below. It works fine with std::sort, but fails to compile in the constructor of std::priority_queue. What is the problem? Thanks.
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
bool comp(int a, int b) { return a > b; }
int main()
{
vector<int> vec = {4, 2, 1, 3};
sort(vec.begin(), vec.end(), comp); // OK
priority_queue<int> q1(less<int>(), vec); // OK
priority_queue<int> q2(comp, vec); // Fail
return 0;
}
Error message:
error: no matching function for call to 'std::priority_queue<int>::priority_queue(bool (&)(int, int), std::vector<int>&)'
priority_queue<int> q2(comp, vec);
^
The type of the default comparator of std::priority_queue is std::less<T> where T is the value type. You are passing something of type bool(*)(int, int) instead. std::sort() being a function can deduce the comparator's type. Class types can't deduce their template arguments (yet - there us discussion in the C++ committee that a future version may have class templates whose template arguments can be deduced.
You can use
std::priority_queue<int, std::vector<int>, bool(*)(int, int)> q(comp);
or, avoiding a hard-to-inline function pointer:
std::priority_queue<int, std::vector<int>, std::greater<int> > q;