Unexpected lifetime of lambda in pipeline - c++

I'm using a pipeline of ranges views and I want to do a complete transformation of the output and keep the pipeline going. I understand that I can't return a copy of the new range because it wouldn't live anywhere. What I don't understand is why I can't keep the storage in the lambda's closure.
auto counter = []() {
std::map<int, int> counts;
return ranges::make_pipeable([=](auto &&rng) mutable -> std::map<int, int>& {
// Do stuff that fills in the map
return counts;
});
};
auto steps = foo() | counter() | bar();
auto data = // stuff
ranges::for_each(data | steps, [](auto &&i) {
std::cout << i << std::endl;
});
When I do a release build this works, but when I do a debug build this segfaults. If I change the map in the counter lambda to be static and the capture to be by reference then this works of course (but is clearly ugly as anything).
What I don't really understand is why the lifetime of the lambda returned from counter (and it's attendant closure) isn't at least as long as the lifetime of the variable steps.

That's because make_pipeable takes it argument by value. Here is the definition from the src code:
struct make_pipeable_fn
{
template<typename Fun>
detail::pipeable_binder<Fun> operator()(Fun fun) const
{
return {std::move(fun)};
}
};
Since you are returning std::map<int, int> by reference, it is pointing to an address of an object which has been moved from. (std::move in the above call)
If this is not super clear, consider a simpler example. In the below code, Noisy is just a struct which emits out its calls.
#include <iostream>
#include <range/v3/utility/functional.hpp>
using namespace ranges;
struct Noisy
{
Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }
// global object counter
static int cnt_;
// local count idx
int local_ = 0;
};
int Noisy::cnt_ = 0;
auto counter = []()
{
Noisy n;
return make_pipeable([=](auto x) { return n; });
};
int main(int argc, char *argv[])
{
auto steps = counter();
std::cout << "Deleting" << '\n';
return 0;
}
The output of the above code is this:
Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Deleting
~Noisy() dtor with local 4
As you can see, object 2 (which in your case is holding the std::map<int, int>) is destroyed before the line printing Deleting. steps is a pipeable_binder struct which extends from our passed lambda and is destroyed at the end.

So I took #skgbanga's code and re-wrote it so it works more like the actual situation. This shows that the application of the steps to the data results in extra copies of the lambdas captured by the pipeline.
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
struct Noisy {
Noisy() { local_ = ++cnt_; std::cout << "Noisy() ctor " << local_ << '\n'; }
Noisy(const Noisy&) { local_= ++cnt_; std::cout << "Noisy(Noisy&) copy ctor " << local_ << '\n'; }
Noisy(Noisy&&) { local_ = ++cnt_; std::cout << "Move constructor " << local_ << '\n'; }
~Noisy() { std::cout << "~Noisy() dtor with local " << local_ << '\n'; }
// global object counter
static int cnt_;
// local count idx
int local_ = 0;
// Make this a range as well. Empty is fine
std::vector<int> range;
auto begin() { return range.begin(); }
auto end() { return range.end(); }
};
int Noisy::cnt_ = 0;
auto counter = []() {
Noisy n;
return ranges::make_pipeable([=](auto x) mutable -> Noisy& {
std::cout << "Returning Noisy range " << n.local_ << '\n';
return n;
});
};
int main(int argc, char *argv[])
{
auto steps = counter();
std::vector<int> data{{1, 2}};
std::cout << "Applying" << '\n';
auto result = data | steps;
std::cout << "Displaying" << '\n';
ranges::for_each(result, [](auto&& v) {
std::cout << "Local result " << v << '\n';
});
std::cout << "Deleting" << '\n';
return 0;
}
The output is:
Noisy() ctor 1
Noisy(Noisy&) copy ctor 2
Move constructor 3
Move constructor 4
~Noisy() dtor with local 3
~Noisy() dtor with local 2
~Noisy() dtor with local 1
Applying
Noisy(Noisy&) copy ctor 5
Noisy(Noisy&) copy ctor 6
Returning Noisy range 6
~Noisy() dtor with local 6
Noisy(Noisy&) copy ctor 7
~Noisy() dtor with local 5
Displaying
Deleting
~Noisy() dtor with local 7
~Noisy() dtor with local 4
Noisy 6 is the one that returns the range and because it was a copy used in the evaluation of the pipe it is almost immediately destructed. This then causes the segfault.
I expect that the extra copies shouldn't really happen.

Related

How to avoid dangling reference with R-value

Some discussion warns about dangling reference, with R-Value reference. I do not see any dangling reference in the following example as DTOR was called when main() terminates. Am I missing something?
class test_ctor
{
public:
explicit
test_ctor(int x = 1):_x(x)
{
std::cout << "CTOR: " << _x << "\n";
}
~test_ctor()
{
std::cout << "DTOR: " << _x << "\n";
}
test_ctor(test_ctor const & y) = default;
test_ctor(test_ctor && y) = default;
int _x;
};
test_ctor test_rvalue()
{
test_ctor test = test_ctor(2);
return test;
}
Now I can use the above code in two ways:
int main(int argc, const char * argv[]) {
auto test = test_rvalue();
std::cout << " test: " << test._x << " \n";
return 0;
}
Or
int main(int argc, const char * argv[]) {
auto && test = test_rvalue();
std::cout << " test: " << test._x << " \n";
return 0;
}
both case has the same output:
CTOR: 2
test: 2
DTOR: 2
Which means both are efficient ways to return object. Are there any side effects in r-value reference?
In c++11:
auto test = test_rvalue(); moves the return value of test_rvalue() into test. This move is then elided by every non-brain damaged compiler so it never happens. The move constructor still needs to exist, but is never called, and any side effects of moving do not occur.
auto&& test = test_rvalue(); binds an rvalue reference to the temporary returned by test_rvalue(). The temporaries lifetime is extended to match that of the reference.
In c++17:
auto test = test_rvalue(); the prvalue return value of test_rvalue() is used to directly construct the variable test. No move, elided or not, occurs.
The auto&& case remains unchanged from c++11.

C++ Destructor thru reference

I want to share with you a tiny problem that I'm not getting to work out, here is the code (it's for test only):
#include <windows.h>
#include <iostream>
#include <vector>
#include <string>
#include <utility>
#include <type_traits>
#include <sstream>
struct Procedure {
Procedure(HANDLE)
{ std::cout << "ctor w/connection: " << this << std::endl; }
~Procedure()
{ std::cout << "dtor: " << this << std::endl; }
Procedure(Procedure &&rhs) {
std::cout << "ctor w/move: " << this << std::endl;
this->m_Params = std::move(rhs.m_Params);
}
Procedure& operator= (Procedure &&rhs) {
std::cout << "operator= w/move: " << this << std::endl;
if (this != &rhs) this->m_Params = std::move(rhs.m_Params);
return *this;
}
Procedure& AppendParam(const std::string &str) {
std::cout << "appendparam: " << this << std::endl;
m_Params.push_back(str);
return *this;
}
void Execute( const std::string &str) {
std::stringstream ss;
ss << str << '(';
for (int i = 0, mx = m_Params.size(); i < mx; ++i) {
ss << '\'' << m_Params[i] << '\'';
if (i < mx - 1) ss << ',';
}
ss << ");";
std::cout << "calling: " << this << " : " << ss.str() << std::endl;
}
private:
Procedure(const Procedure &) = delete;
Procedure& operator=(const Procedure &) = delete;
std::vector<std::string> m_Params;
};
Procedure ProcedureCaller()
{ return Procedure(nullptr); }
int __cdecl main() {
std::cout << "test1---------------------" << std::endl; {
auto &proc = ProcedureCaller().AppendParam("param_1").AppendParam("param_2");
proc.Execute("sp_test");
}
std::cout << "test2--------------------" << std::endl; {
auto proc = ProcedureCaller();
proc.AppendParam("param_A").AppendParam("param_B");
proc.Execute("sp_test_2");
}
std::cout << "test3--------------------" << std::endl; {
ProcedureCaller().AppendParam("param_AA").AppendParam("param_BB").Execute("sp_test_2");
}
return 0;
}
And here is the result I'm getting:
test1---------------------
ctor w/connection: 00F8FC98
appendparam: 00F8FC98
appendparam: 00F8FC98
dtor: 00F8FC98
calling: 00F8FC98 : sp_test();
test2--------------------
ctor w/connection: 00F8FD70
appendparam: 00F8FD70
appendparam: 00F8FD70
calling: 00F8FD70 : sp_test_2('param_A','param_B');
dtor: 00F8FD70
test3--------------------
ctor w/connection: 004FFB20
appendparam: 004FFB20
appendparam: 004FFB20
calling: 004FFB20 : sp_test_2('param_AA','param_BB');
dtor: 004FFB20
I have a few questions:
1- Why dtor of "test1" is getting called before the end of its scope? I mean, the code hasn't even called the Execute method.
2- If dtor of "test1" is a temporal object, why I'm not seeing a log from the move ctor, or at least a compiler error because it's trying to use the deleted copy ctor?
3- What's the difference between "test1" and "test2", I want to be able to call the Execute whatever way I want.
4- What am I missing?
Thanks.
Here's a simpler version demonstrating the same problem:
struct X {
X() = default;
~X() { std::cout << "dtor\n"; }
X& self() { return *this; }
};
int main()
{
X& x = X().self();
std::cout << "here?\n";
}
This program prints dtor before it prints here. Why? The problem is, we have a temporary (X()) that does not get lifetime extended, so it gets destroyed at the end of the expression that contains it (which is X().self()). While you get a reference to it, it's not one of the magic references that does lifetime extension - what you get is just a reference to an object that's immediately going out of scope.
Lifetime extension only happens under very limited circumstances. The temporary has to be bound immediately to a reference, which can only happen for const references:
X const& x = X();
std::cout << "here\n";
Now this prints here before dtor.
Additionally, there is no transitive lifetime extension. Even if in the original example we did:
X const& x = X().self();
We'd still get a dangling reference.
In the "test1" case, the object referenced by proc is a temporary. It goes out of scope at the end of the full expression in which it was created, and proc is immediately left dangling. Note that no lifetime extension happens for two reasons: a) lifetime extension only happens with const lvalue-references and rvalue-references and b) lifetime extension only happens with prvalues, while the reference returned by AppendParam is an lvalue.
proc in the "test1" case is a reference, not an object. No move or copy happens because there is no object to be moved or copied to. A reference is bound to the temporary object returned by ProcedureCaller, and it goes out of scope at the next ;.
The difference between "test1" and "test2" is that proc is a reference in "test1" and an actual object in "test2". If you tried to make proc a reference in the "test2" case the compiler would complain. Only const lvalue-references and rvalue-references may be bound to a prvalue (such as the object returned from a function). The only reason it works in the "test1" case is that you've "laundered" the prvalue through a method that returns an lvalue-reference.

Simple C++ Program Compiled without Optimization by GCC doesn't Generate Expected Results

Today when I tried to compile a very simple C++ program using GCC7, I met a very strange problem: the program didn't add any elements to a vector in the constructor, when compiled without optimization (e.g. -O0/-Og) by the GCC 7.2.1 from Devtoolset-7 on Red Hat Enterprise Linux 7. Only when the optimization switch was added (e.g. -O/-O1/-O2/...), the compiled binary can generated expected results. But why is this happening?
By the way:
without optimization, the binaries compiled by GCC 7.2.1 on RHEL7 and GCC 7.3.0 on Mac (Homebrew version) behaved differently: the former didn't add any elements, while the latter add 2 elements.
clang doesn't have this problem no matter the optimization is turned on or not)
The code:
#include <vector>
#include <utility>
#include <iostream>
class Container
{
std::vector<std::size_t> elements;
public:
Container() {}
Container(std::size_t n)
{
std::cout << "Creating " << n << " elements:";
for(int i; i<n; ++i)
{
std::cout << " " << i+1;
elements.push_back(i+1);
}
std::cout << '\n';
}
Container(Container& c) : elements{c.elements} {}
Container(Container&& c) : elements{std::move(c.elements)} {}
virtual ~Container() noexcept {}
Container& operator=(const Container& c)
{
if(this != &c)
{
elements = c.elements;
}
return *this;
}
Container& operator=(Container&& c)
{
if(this != &c)
{
elements = std::move(c.elements);
}
return *this;
}
void print()
{
std::cout << "Container has " << elements.size() << " elements:" << '\n';
for(auto it=elements.cbegin(); it!=elements.cend(); ++it)
{
if(it == elements.cbegin()) std::cout << *it;
else std::cout << ", " << *it;
}
if(elements.size()>0) std::cout << '\n';
}
};
Container makeContainer()
{
std::cout << "Inside makeContainer()" << '\n';
std::cout << "Before:" << '\n';
Container c(3);
c.print();
std::cout << "Temporary:" << '\n';
Container c_tmp(3);
c_tmp.print();
c = c_tmp;
std::cout << "After:" << '\n';
c.print();
return c;
};
int main()
{
Container c = makeContainer();
std::cout << "Inside main()" << '\n';
c.print();
return 0;
}
Expected output:
Inside makeContainer()
Before:
Creating 3 elements: 1 2 3
Container has 3 elements:
1, 2, 3
Temporary:
Creating 3 elements: 1 2 3
Container has 3 elements:
1, 2, 3
After:
Container has 3 elements:
1, 2, 3
Inside main()
Container has 3 elements:
1, 2, 3
Actual output:
Inside makeContainer()
Before:
Creating 3 elements:
Container has 0 elements:
Temporary:
Creating 3 elements:
Container has 0 elements:
After:
Container has 0 elements:
Inside main()
Container has 0 elements:
If you do not assign a value to a variable its state is indeterminate.
In debug mode the compiler can put the value zero to initialize indeterminate values to help with debugging. But in release this extra unasked for initialization will not happen.
for(int i; i<n; ++i) // Here you have declared `i` but not initialized it.
As a result in release mode the value is probably larger than n and thus no elements are inserted.
Note: It is UB to read the value of an initialized variable (so your whole program can do anything).

Does this use of std::make_unique lead to non-unique pointers?

Suppose I have the following code in C++:
#include <memory>
#include <iostream>
struct Some {
Some(int _a) : a(_a) {}
int a;
};
int main() {
Some some(5);
std::unique_ptr<Some> p1 = std::make_unique<Some>(some);
std::unique_ptr<Some> p2 = std::make_unique<Some>(some);
std::cout << p1->a << " " << p2->a << std::endl;
return 0;
}
As I understand, unique pointers are used to guarantee that resources are not shared. But in this case both p1 and p2 point to the same instance some.
Please unveil the situation.
They don't point to the same resource, they each point to a different copy of it. You can illustrate it by deleting the copy constructor to see the error:
#include <memory>
#include <iostream>
struct Some {
Some(int _a) : a(_a) {}
Some(Some const&) = delete;
int a;
};
int main() {
Some some(5);
std::unique_ptr<Some> p1 = std::make_unique<Some>(some); //error here
std::unique_ptr<Some> p2 = std::make_unique<Some>(some);
std::cout << p1->a << " " << p2->a << std::endl;
return 0;
}
std::make_unique creates objects, calling constructor with specified arguments.
You passed Some& as parameter, and here copy constructor was invoked, and new object constructed.
So, p1 and p2 are 2 absolutely different pointers, but constructed from same object, using copy constructor
both p1 and p2 point to the same instance some
No, they don't.
#include <memory>
#include <iostream>
struct Some {
Some(int _a) : a(_a) {}
int a;
};
int main() {
Some some(5);
std::unique_ptr<Some> p1 = std::make_unique<Some>(some);
std::unique_ptr<Some> p2 = std::make_unique<Some>(some);
std::cout << p1->a << " " << p2->a << std::endl;
p1->a = 42;
std::cout << p1->a << " " << p2->a << std::endl;
return 0;
}
output:
5 5
42 5
To test whether two pointers point to the same object instance, you should compare the locations they point to, instead of the object member variables:
std::cout << &(*p1) << " " << &(*p2) << std::endl;
Which will show that they indeed do not point to the same instance.
ADDENDUM: As pointed out by Remy Lebeau, since C++11 it is advisable to use the
std::addressof function for this purpose, which obtains the actual address of an object even if the & operator is overloaded:
std::cout << std::addressof(*p1) << " " << std::addressof(*p2) << std::endl;

Copy elision on Visual C++ 2010 Beta 2

I was reading Want Speed? Pass by Value on the C++ Next blog and created this program to get a feel for copy elision and move semantics in C++0x:
#include <vector>
#include <iostream>
class MoveableClass {
public:
MoveableClass() : m_simpleData(0), instance(++Instances) {
std::cout << "Construct instance " << instance << " (no data)" << std::endl;
}
MoveableClass(std::vector<double> data) : m_data(std::move(data)), m_simpleData(0), instance(++Instances) {
std::cout << "Construct instance " << instance << " (with data)" << std::endl;
}
MoveableClass(int simpleData) : m_simpleData(simpleData), instance(++Instances) {
std::cout << "Construct instance " << instance << " (with simple data)" << std::endl;
}
MoveableClass(const MoveableClass& other)
: m_data(other.m_data), m_simpleData(other.m_simpleData), instance(++Instances)
{
std::cout << "Construct instance " << instance << " from a copy of " << other.instance << std::endl;
Elided = false;
}
MoveableClass(MoveableClass&& other)
: m_data(std::move(other.m_data)), m_simpleData(other.m_simpleData), instance(++Instances)
{
std::cout << "Construct instance " << instance << " from a move of " << other.instance << std::endl;
Elided = false;
}
MoveableClass& operator=(MoveableClass other) {
std::cout << "Assign to instance " << instance << " from " << other.instance << std::endl;
other.Swap(*this);
return *this;
}
~MoveableClass() {
std::cout << "Destroy instance " << instance << std::endl;
--Instances;
}
void Swap(MoveableClass& other) {
std::swap(m_data, other.m_data);
std::swap(m_simpleData, other.m_simpleData);
}
static int Instances;
static bool Elided;
private:
int instance;
int m_simpleData;
std::vector<double> m_data;
};
int MoveableClass::Instances = 0;
bool MoveableClass::Elided = true;
std::vector<double> BunchOfData() {
return std::vector<double>(9999999);
}
int SimpleData() {
return 9999999;
}
MoveableClass CreateRVO() {
return MoveableClass(BunchOfData());
}
MoveableClass CreateNRVO() {
MoveableClass named(BunchOfData());
return named;
}
MoveableClass CreateRVO_Simple() {
return MoveableClass(SimpleData());
}
MoveableClass CreateNRVO_Simple() {
MoveableClass named(SimpleData());
return named;
}
int main(int argc, char* argv[]) {
std::cout << "\nMove assign from RVO: " << '\n';
{
MoveableClass a;
a = CreateRVO();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from RVO simple: " << '\n';
{
MoveableClass a;
a = CreateRVO_Simple();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from NRVO: " << '\n';
{
MoveableClass a;
a = CreateNRVO();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
std::cout << "\nMove assign from NRVO simple: " << std::endl;
{
MoveableClass a;
a = CreateNRVO_Simple();
}
std::cout << "Move elided: " << (MoveableClass::Elided ? "Yes" : "No") << '\n';
MoveableClass::Elided = true; // reset for next test
}
Here is the output I get when compiled in release mode on Visual C++ 10.0 (Beta 2):
Move assign from RVO:
Construct instance 1 (no data)
Construct instance 2 (with data)
Construct instance 3 from a move of 2
Destroy instance 2
Assign to instance 1 from 3
Destroy instance 3
Destroy instance 1
Move elided: No
Move assign from RVO simple:
Construct instance 1 (no data)
Construct instance 2 (with simple data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
Move assign from NRVO:
Construct instance 1 (no data)
Construct instance 2 (with data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
Move assign from NRVO simple:
Construct instance 1 (no data)
Construct instance 2 (with simple data)
Assign to instance 1 from 2
Destroy instance 2
Destroy instance 1
Move elided: Yes
However, I am perplexed by one thing. As you can see, all of the moves are elided except for the first one. Why can't the compiler perform RVO with a MoveableClass(std::vector) at line 86, but can with a MoveableClass(int) at line 97? Is this just a bug with MSVC or is there a good reason for this? And if there is a good reason, why can it still perform NRVO on a MoveableClass(std::vector) at line 91?
I'd like to understand it so I can go to sleep happy. :)
Thanks for replying Dave.
I've added my tests to that example:
pastebin.com/f7c8ca0d6
Curiously it shows that all types of elisions are not being performed except for NRVO!
Edit: Actually I suppose this is because it is the only test where the object ever has a name.
I also tried other STL types and got the same result. However when trying my own non-pod types it works as expected. I can't think what's special about the STL types that could be causing this so I don't know what else to try.
I'll submit a bug report.
Edit: Submitted here
Thanks
Hmm.
It seems that if you change the data constructor
MoveableClass::MoveableClass(std::vector<double> data)
to accept the vector by reference, like so,
MoveableClass::MoveableClass(const std::vector<double>& data)
it works fine! Why does it not work if you pass the vector by value?
Also here's a version that should compile on earlier versions of MSVC, if anybody wants to run the test there. It contains no C++0x features: http://pastebin.com/f3bcb6ed1
Maybe it'd be a good idea to update and maintain this example from cpp-next with a version of your test that fails, so there can be one comprehensive, canonical test.