Vector of pair with const member - c++

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.

Related

Why can logical constness only be added to a std::span of const pointers?

Consider this code that attempts to create various std::span objects for a vector of raw pointers.
#include <vector>
#include <span>
int main()
{
struct S {};
std::vector<S*> v;
std::span<S*> span1{v};
std::span<S* const> span2{v};
std::span<const S* const> span3{v};
std::span<const S*> span4{v};
return 0;
}
span3 compiles fine, but span4 fails with the following error:
<source>: In function 'int main()':
<source>:58:32: error: no matching function for call to 'std::span<const main()::S*>::span(<brace-enclosed initializer list>)'
58 | std::span<const S*> span4{v};
| ^
In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/ranges:45,
from <source>:5:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/span:231:9: note: candidate: 'template<class _OType, long unsigned int _OExtent> requires (_Extent == std::dynamic_extent || _OExtent == std::dynamic_extent || _Extent == _OExtent) && (std::__is_array_convertible<_Type, _Tp>::value) constexpr std::span<_Type, _Extent>::span(const std::span<_OType, _OExtent>&) [with long unsigned int _OExtent = _OType; _Type = const main()::S*; long unsigned int _Extent = 18446744073709551615]'
I would either expected span3 and span4 both to fail or both to succeed. Can someone explain why logical constness can be added to to a std::span of raw pointers iff the underlying pointer is const (i.e. bitwise).
std::span<const S*> allows you to assign a const S* to an element.
std::vector<S*> allows you to read an element of type S*.
If std::span<const S*> were allowed to take a std::vector<S*>, then it would be possible to sneakily convert a const S* to a S*, by assigning the const S* to an element of the span and then reading the same element through the vector.
That is to say, if std::span<const S*> span4{v}; were allowed, then the following program would be valid:
#include <vector>
#include <span>
int main()
{
struct S { int value; };
std::vector<S*> v = {nullptr};
std::span<const S*> span4{v}; // Note: not allowed in reality
const S s{.value = 0};
span4[0] = &s;
S* p = v[0];
assert(p == &s);
p->value = 42; // Oops, modifies the value of a const object!
}
So in order to prevent this scenario and provide const-correctness, std::span<const S*> must not be constructible from a std::vector<S*>.
On the other hand, std::span<const S* const> does not allow you to assign to its elements, so it's safe for it to take a std::vector<S*>.
(Yes, it's the same reason that you can convert a S** to const S* const *, but you cannot convert it to const S**.)

template parameter inference for std pair [duplicate]

This question already has answers here:
Template argument deduction for implicit pair
(2 answers)
Closed 1 year ago.
In the code below, X x1{{100,5.55}, "abc"} fails to compile ("candidate template ignored: couldn't infer template argument 'T1'").
I believe that this is because, template deduction does not consider implicit conversions, so the compiler does not treat the first argument as a std::pair.
Is there any way to make the X x1{{100,5.55}, "abc"} syntax work? I want to avoid explicit use of std::pair in the argument, because I am refactoring a large code base where this expression is used in many places.
(In the current code, the constructor is not templated, because it uses std::variant to create an omnibus class that handles multiple types with implicit constructors for int, double, std::string, etc. I am trying to replace this class with a templated constructor that preserves the underlying data types, to improve type safety.)
#include <utility>
struct X
{
template <class T1, class T2> X(std::pair<T1,T2>, const char*){}
};
int main()
{
X x{std::pair{100,5.55}, "abc"}; // OK
// X x1{{100,5.55}, "abc"}; // Compile error
}
I don't have a perfect solution, but if I am understanding your constraints well enough, it is not a big deal if new code needs to explicitly use std::pair{}, but old code has to remain compatible.
What about introducing a simple compatibility struct and (ab)using aggregate initialization to provide a constructor overload?
#include <utility>
#include <iostream>
struct X
{
template <class T1, class T2> X(std::pair<T1,T2>, const char*)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
}
struct LegacyPair
{
int a;
double b;
};
X(LegacyPair p, const char* text) :
X(std::pair{p.a, p.b}, text)
{}
};
int main()
{
X x1{std::pair{100,5.55}, "abc"};
X x2{{100,5.55}, "abc"};
//X x3{{3.14,5}, "abc"}; // error
X x4{std::pair{3.14,5}, "abc"};
}
This outputs:
X::X(std::pair<_T1, _T2>, const char*) [with T1 = int; T2 = double] for x1
X::X(std::pair<_T1, _T2>, const char*) [with T1 = int; T2 = double] for x2
An error for x3 (better than accidentally constructing a std::pair<int, double>). Because {} is aggregate initialization here, implicit conversion is not allowed, so this overload gets discarded. Which is good!
X::X(std::pair<_T1, _T2>, const char*) [with T1 = double; T2 = int] for x4

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.

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

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.

Cannot construct an std::map with a customer compare functor?

#include <map>
using namespace std;
class C {
public:
C(map<int,int> m) { }
int operator()(int a, int b) {
return a < b;
}
};
int main() {
map<int, int> m;
map<int, int, C> mymap(C(m));
mymap.insert(pair<int,int>(1,1));
}
Why do I get the following error?:
main.cpp: In function 'int main()':
main.cpp:16:11: error: request for member 'insert' in 'mymap', which is of non-class type 'std::map<int, int, C>(C)'
mymap.insert(pair<int,int>(1,1));
Here is the coliru link: http://coliru.stacked-crooked.com/a/0413a35d3177ef48
This is an example of a vexing parse - function declaration, where you'd expect an object.
Try this:
map<int, int, C> mymap((C(m)));
map<int, int, C> mymap(C(m));
In this mymap is taken as a function. Change it to
map<int, int, C> mymap((C(m)));
In C++11 you can also avoid vexing parse by using brace initializer:
map<int, int, C> mymap(C{m});
(Though if would behave differently if C had a constructor C(std::initializer_list<T>) where map<int, int> would be implicitly convertible to T, so that constructor would be called instead)
Update: as Benjamin Bannier pointed out, you can use brace initializers everywhere:
map<int, int, C> mymap{C{m}};
or
map<int, int, C> mymap{C(m)};
With the same precaution: C shouldn't have operator std::pair<const int, int>().