We have following code(it's more complicated ofc, I tried to make a minimal example).
#include <iostream>
#include <vector>
#include <string>
#include <memory>
template<typename T>
struct use_type
{
use_type(T& v) : value(v) {}
T& value;
};
template<typename T>
use_type<T> use(T& value) { return use_type<T>(value); }
template<typename T>
use_type<T> use(const T& value) { return use_type<T>(const_cast<T&>(value)); }
template<typename T>
struct printer_helper
{
void use(const use_type<T>& use) { uses.push_back(use); }
void final_action()
{
for (const auto& use : uses)
{
std::cout << use.value.size() << std::endl;
for (const auto& v : use.value)
{
std::cout << v << ",";
}
std::cout << std::endl;
}
}
std::vector<use_type<T>> uses;
};
template<typename T>
struct printer
{
printer() { helper = new printer_helper<T>(); }
printer<T>& operator , (const use_type<T>& t)
{
helper->use(t);
return *this;
}
~printer()
{
helper->final_action();
delete helper;
}
printer(const printer&) = delete;
printer& operator =(const printer&) = delete;
printer(printer&&) = default;
printer& operator =(printer&&) = default;
printer_helper<T>* helper;
};
template<typename T>
struct printer_creator
{
printer<T> operator << (const char*) { return {}; }
};
int main()
{
using vec = std::vector<std::string>;
{
/*vec v1{"abc", "bcd"};
vec v2{"new", "old", "real"};*/
printer_creator<vec> p;
p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});
//p << p, use(v1), use(v2);
}
}
This code works perfectly fine on g++ before g++-11. With g++-11 it's either segfault or trash in the output. Is it UB? And if yes, probably you can tell me, which gcc change makes it fail and quotes from standard if possible?
Live examples:
g++-10 g++-11
The temporary vectors are destructed at the end of the line where they are created. p
is destructed at the end of its scope (i.e. after the vectors). As the destructor of p
uses references to the vectors which are now dangling your code has undefined behaviour.
If it ever worked that was pure "luck" that the vector destructors left the vectors in a usable state.
In
p << "", use(vec{"abc", "bcd"}), use(vec{"new", "old", "real"});
You create 3 temporaries
printer<vec>
from p << ""
)vec{"abc", "bcd"}
vec{"new", "old", "real"}
without sequence between them (see note below), so they may be created in any order.
Only destruction from left to right is correct (so construction from right to left), other order would use dangling reference and is UB. And as you don't have guarantee of order...
Note:
We lose short-circuit by overloading operator &&
/operator ||
,
we lose left to right evaluation order by overloading operator,
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With