Std::Copy to Std::Cout for Std::Pair

std::copy to std::cout for std::pair

I've founded one new elegant way to solve this problem.

I've got many interest ideas when read answers:

  • wrap iterator, for transform std::pair to std::string;
  • wrap std::pair, for have a chance to overload operator<<(...);
  • use usual std::for_each with printing functor;
  • use std::for_each with boost::labda - looks nice, except accessing to std::pair< >::first and std::pair< >::second members;

I think I will use all of this ideas in future for solve different other problems.

But for this case I've understaded that I can formulate my bproblem as "transform map's data to strings and write them to output stream" instead "copy map's data to ouput stream". My solution looks like:

namespace
{
std::string toString( const std::pair< size_t, size_t >& data)
{
std::ostringstream str;
str << data.first << ", " << data.second;
return str.str();
}
} // namespace anonymous

std::transform(
some_map.begin(),
some_map.end(),
std::ostream_iterator< std::string >( std::cout, "\n" ),
toString );

I think this method is most short and expressive than others.

Does copy operator= exist for std::pair

std::pair is copyable only as long as whatever's in a std::pair is copyable. If you think about, for a few seconds, you will agree that this makes 100% sense.

std::pair<std::string, std::ifstream> 

std::ifstream is not copyable. You cannot copy std::ifstreams. Putting it inside a std::pair doesn't make it copyable.

But that's not the least of the problems in the shown code:

std::map<std::string, std::ifstream> files;
std::transform(mFileMap.begin(), mFileMap.end(), files.begin(),
// ...

The files map is empty. files.begin() will return the beginning iterator to an empty map, which will be the same value as files.end(). Attempting to copy into an ending iterator (ignoring the fact that the underlying type is not copyable) will not end well.

Additionally, std::map's key is constant, in the map, so that won't work either.

This is not how stuff gets added to a map, one needs to use a std::insert_iterator.

So, to summarize, the following problems must be solved:

  1. This std::pair is not copyable.
  2. A std::insert_iterator must be used to insert new key/value pairs into a std::map.

Presuming you insist on your std::map containing these values, you'll have to do some work to populate it, using std::map::emplace, this will be the most practical way to drop new things into this map.

Returning an std::pairstd::shared_ptrA, std::unique_ptrB& from a function results in weirdness

std::pair<std::shared_ptr<A>, std::unique_ptr<B>&> FuncA() {
// ...
std::unique_ptr<B> b = std::make_unique<B>();
// ...
return {a,b};
}

A local std::unique_ptr<B> is created and a reference to it is returned as the second element in the pair. This is a dangling reference and is later accessed, giving the program undefined behaviour.

Why is std::pair from anonymous object copying that object instead of moving?

There's a problem with how std::pair is defined. I'd even say it's a minor defect in the standard.

It has two constructors that could be used here:

  1. pair(const T1 &x, const T2 &y);, where T1, T2 are template parameters of the pair.

  2. template <class U1, class U2> pair(U1 &&x, U2 &&y);

If you do std::pair<A, A> a{A{}, A{}});, then the second constructor is selected and all is well.

But if you do std::pair<A, A> a{{}, {}};, the compiler can't use the second constructor because it can't deduce U1, U2, because {} by itself has no type. So the first constructor is used and you get a copy.

For it to work properly, std::pair should have an extra non-template constructor pair(T1 &&, T2 &&), and for a good measure two extra constructors: pair(const T1 &, T2 &&) and pair(T1 &&, const T2 &).

Copy vs move in std::pair braced initialization

Let's take a look at two of the two-argument constructors of pair: [pairs.pair]

EXPLICIT constexpr pair(const T1& x, const T2& y);
template<class U1, class U2> EXPLICIT constexpr pair(U1&& x, U2&& y);

The second constructor uses perfect forwarding, and U2 cannot be deduced from {}. Therefore, the first version is selected when {} is used. When Foo{} is used instead, the argument has type Foo, so U2 is deduced to be Foo, causing the forwarding version to be selected.

How to update values in C++ std::pairint, int

Your first function has a bug. It reports when a robot is not found, but still dereferences the end iterator, which causes undefined behavior. Instead, you should return a pointer, which is conditionally null:

// Returns null if the robot is not found:
std::pair<int, int>*
World::getRobotLocation(char robot_name){
auto const location = robots.find(robot_name);
if (location == robots.end()) {
return nullptr;
}
return &location->second;
}

And in your other function, you check to see, if the pointer is not null, you update the value:

// Returns true if move happens, 
// false otherwise.
bool
move(char robot, char direction) {
auto const robot_location = World::getRobotLocation(robot);

if (!robot_location) return false;

switch (direction) {
case 'L': {
++robot_location->first;
} break;
case 'D': {
--robot_location->second;
} break;
case 'R': {
--robot_location->first;
} break;
default: {
++robot_location->second;
} break;
}
return true;
}

Returning a pair of objects

For the second snippet,

auto f() {
std::vector<int> v0(100000);
std::vector<int> v1(100000);
return std::make_pair(std::move(v0),std::move(v1)); // is the move needed?
}

return returns the result of the std::make_pair() function. That's an RValue.

However, the OP's question probably condenses to whether (or why not) Named Return Value Optimization still applies to v0/v1 when returned as a std::pair.

Thereby, it's overlooked that v0/v1 aren't subject of return anymore, but become arguments of std::make_pair(). As such, v0/v1 are LValues – std::move(v0), std::move(v1) have to be applied to turn them into RValues if move-semantic is intended.


Demo on coliru:

#include <iostream>

template <typename T>
struct Vector {
Vector(size_t n)
{
std::cout << "Vector::Vector(" << n << ")\n";
}
Vector(const Vector&)
{
std::cout << "Vector::Vector(const Vector&)\n";
}
Vector(const Vector&&)
{
std::cout << "Vector::Vector(const Vector&&)\n";
}

};

auto f1() {
Vector<int> v(100000);
return std::move(v); // over-pessimistic
}

auto f2() {
Vector<int> v(100000);
return v; // allows NRVO
}

auto f3() {
Vector<int> v0(100000);
Vector<int> v1(100000);
return std::make_pair(v0, v1); // copy constructor called for v0, v1
}

auto f4() {
Vector<int> v0(100000);
Vector<int> v1(100000);
return std::make_pair(std::move(v0),std::move(v1)); // move constructor called for v0, v1
}

#define DEBUG(...) std::cout << #__VA_ARGS__ << ";\n"; __VA_ARGS__

int main()
{
DEBUG(f1());
DEBUG(f2());
DEBUG(f3());
DEBUG(f4());
}

Output:

f1();
Vector::Vector(100000)
Vector::Vector(const Vector&&)
f2();
Vector::Vector(100000)
f3();
Vector::Vector(100000)
Vector::Vector(100000)
Vector::Vector(const Vector&)
Vector::Vector(const Vector&)
f4();
Vector::Vector(100000)
Vector::Vector(100000)
Vector::Vector(const Vector&&)
Vector::Vector(const Vector&&)

Why doesn't RVO happen with structured bindings when returning a pair from a function using std::make_pair?

std::make_pair is a function that takes the arguments by reference. Therefore temporaries are created from the two Test() arguments and std::make_pair constructs a std::pair from these, which requires copy-constructing the pair elements from the arguments. (Move-constructing is impossible since your manual definition of the copy constructor inhibits the implicit move constructor.)

This has nothing to do with structured bindings or RVO or anything else besides std::make_pair.

Because std::pair is not an aggregate class, you cannot solve this by simply constructing the std::pair directly from the two arguments either. In order to have a std::pair construct the elements in-place from an argument list you need to use its std::piecewise_construct overload:

auto func() {
return std::pair<Test, Test>(std::piecewise_construct, std::forward_as_tuple(), std::forward_as_tuple());
}


Related Topics



Leave a reply



Submit