I have a service which has outages in 4 different locations. I am modeling each location outages into a Boost ICL interval_set. I want to know when at least N locations have an active outage.
Therefore, following this answer, I have implemented a combination algorithm, so I can create combinations between elemenets via interval_set intersections.
Whehn this process is over, I should have a certain number of interval_set, each one of them defining the outages for N locations simultaneusly, and the final step will be joining them to get the desired full picture.
The problem is that I'm currently debugging the code, and when the time of printing each intersection arrives, the output text gets crazy (even when I'm using gdb to debug step by step), and I can't see them, resulting in a lot of CPU usage.
I guess that somehow I'm sending to output a larger portion of memory than I should, but I can't see where the problem is.
This is a SSCCE:
#include <boost/icl/interval_set.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
int main() {
// Initializing data for test
std::vector<boost::icl::interval_set<unsigned int> > outagesPerLocation;
for(unsigned int j=0; j<4; j++){
boost::icl::interval_set<unsigned int> outages;
for(unsigned int i=0; i<5; i++){
outages += boost::icl::discrete_interval<unsigned int>::closed(
(i*10), ((i*10) + 5 - j));
}
std::cout << "[Location " << (j+1) << "] " << outages << std::endl;
outagesPerLocation.push_back(outages);
}
// So now we have a vector of interval_sets, one per location. We will combine
// them so we get an interval_set defined for those periods where at least
// 2 locations have an outage (N)
unsigned int simultaneusOutagesRequired = 2; // (N)
// Create a bool vector in order to filter permutations, and only get
// the sorted permutations (which equals the combinations)
std::vector<bool> auxVector(outagesPerLocation.size());
std::fill(auxVector.begin() + simultaneusOutagesRequired, auxVector.end(), true);
// Create a vector where combinations will be stored
std::vector<boost::icl::interval_set<unsigned int> > combinations;
// Get all the combinations of N elements
unsigned int numCombinations = 0;
do{
bool firstElementSet = false;
for(unsigned int i=0; i<auxVector.size(); i++){
if(!auxVector[i]){
if(!firstElementSet){
// First location, insert to combinations vector
combinations.push_back(outagesPerLocation[i]);
firstElementSet = true;
}
else{
// Intersect with the other locations
combinations[numCombinations] -= outagesPerLocation[i];
}
}
}
numCombinations++;
std::cout << "[-INTERSEC-] " << combinations[numCombinations] << std::endl; // The problem appears here
}
while(std::next_permutation(auxVector.begin(), auxVector.end()));
// Get the union of the intersections and see the results
boost::icl::interval_set<unsigned int> finalOutages;
for(std::vector<boost::icl::interval_set<unsigned int> >::iterator
it = combinations.begin(); it != combinations.end(); it++){
finalOutages += *it;
}
std::cout << finalOutages << std::endl;
return 0;
}
Any help?
As I surmised, there's a "highlevel" approach here.
Boost ICL containers are more than just containers of "glorified pairs of interval starting/end points". They are designed to implement just that business of combining, searching, in a generically optimized fashion.
So you don't have to.
If you let the library do what it's supposed to do:
using TimePoint = unsigned;
using DownTimes = boost::icl::interval_set<TimePoint>;
using Interval = DownTimes::interval_type;
using Records = std::vector<DownTimes>;
Using functional domain typedefs invites a higher level approach. Now, let's ask the hypothetical "business question":
What do we actually want to do with our records of per-location downtimes?
Well, we essentially want to
tally them for all discernable time slots and
filter those where tallies are at least 2
finally, we'd like to show the "merged" time slots that remain.
Ok, engineer: implement it!
Hmm. Tallying. How hard could it be?
❕ The key to elegant solutions is the choice of the right datastructure
using Tally = unsigned; // or: bit mask representing affected locations?
using DownMap = boost::icl::interval_map<TimePoint, Tally>;
Now it's just bulk insertion:
// We will do a tally of affected locations per time slot
DownMap tallied;
for (auto& location : records)
for (auto& incident : location)
tallied.add({incident, 1u});
Ok, let's filter. We just need the predicate that works on our DownMap, right
// define threshold where at least 2 locations have an outage
auto exceeds_threshold = [](DownMap::value_type const& slot) {
return slot.second >= 2;
};
Merge the time slots!
Actually. We just create another DownTimes set, right. Just, not per location this time.
The choice of data structure wins the day again:
// just printing the union of any criticals:
DownTimes merged;
for (auto&& slot : tallied | filtered(exceeds_threshold) | map_keys)
merged.insert(slot);
Report!
std::cout << "Criticals: " << merged << "\n";
Note that nowhere did we come close to manipulating array indices, overlapping or non-overlapping intervals, closed or open boundaries. Or, [eeeeek!] brute force permutations of collection elements.
We just stated our goals, and let the library do the work.
Full Demo
Live On Coliru
#include <boost/icl/interval_set.hpp>
#include <boost/icl/interval_map.hpp>
#include <boost/range.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>
#include <boost/range/numeric.hpp>
#include <boost/range/irange.hpp>
#include <algorithm>
#include <iostream>
#include <vector>
using TimePoint = unsigned;
using DownTimes = boost::icl::interval_set<TimePoint>;
using Interval = DownTimes::interval_type;
using Records = std::vector<DownTimes>;
using Tally = unsigned; // or: bit mask representing affected locations?
using DownMap = boost::icl::interval_map<TimePoint, Tally>;
// Just for fun, removed the explicit loops from the generation too. Obviously,
// this is bit gratuitous :)
static DownTimes generate_downtime(int j) {
return boost::accumulate(
boost::irange(0, 5),
DownTimes{},
[j](DownTimes accum, int i) { return accum + Interval::closed((i*10), ((i*10) + 5 - j)); }
);
}
int main() {
// Initializing data for test
using namespace boost::adaptors;
auto const records = boost::copy_range<Records>(boost::irange(0,4) | transformed(generate_downtime));
for (auto location : records | indexed()) {
std::cout << "Location " << (location.index()+1) << " " << location.value() << std::endl;
}
// We will do a tally of affected locations per time slot
DownMap tallied;
for (auto& location : records)
for (auto& incident : location)
tallied.add({incident, 1u});
// We will combine them so we get an interval_set defined for those periods
// where at least 2 locations have an outage
auto exceeds_threshold = [](DownMap::value_type const& slot) {
return slot.second >= 2;
};
// just printing the union of any criticals:
DownTimes merged;
for (auto&& slot : tallied | filtered(exceeds_threshold) | map_keys)
merged.insert(slot);
std::cout << "Criticals: " << merged << "\n";
}
Which prints
Location 1 {[0,5][10,15][20,25][30,35][40,45]}
Location 2 {[0,4][10,14][20,24][30,34][40,44]}
Location 3 {[0,3][10,13][20,23][30,33][40,43]}
Location 4 {[0,2][10,12][20,22][30,32][40,42]}
Criticals: {[0,4][10,14][20,24][30,34][40,44]}
At the end of the permutation loop, you write:
numCombinations++;
std::cout << "[-INTERSEC-] " << combinations[numCombinations] << std::endl; // The problem appears here
My debugger tells me that on the first iteration numCombinations was 0 before the increment. But incrementing it made it out of range for the combinations container (since that is only a single element, so having index 0).
Did you mean to increment it after the use? Was there any particular reason not to use
std::cout << "[-INTERSEC-] " << combinations.back() << "\n";
or, for c++03
std::cout << "[-INTERSEC-] " << combinations[combinations.size()-1] << "\n";
or even just:
std::cout << "[-INTERSEC-] " << combinations.at(numCombinations) << "\n";
which would have thrown std::out_of_range?
On a side note, I think Boost ICL has vastly more efficient ways to get the answer you're after. Let me think about this for a moment. Will post another answer if I see it.
UPDATE: Posted the other answer show casing highlevel coding with Boost ICL
Related
I have an unordered_map<int, float> localCostMap describing the costs (float) between node IDs (int). The calculation of the second value is quite complex, but due to the structure of graph (directed, acyclic, up to two parent nodes) I can save many calculations by pooling the maps for each node into another map like so:
unordered_map<int, shared_ptr<unordered_map<int, float>>> pooledMaps.
Once the values are written (into localCostMap), they do not get updated again, but the calculations required the map entries of the connected nodes, which may leed to lock-ups.
How can I make it so that I can read the values stored in the inner map while also safely adding new entries (e.g. { 3, 1.23 }? I'm new to multithreading and have tried to search for solutions, but the only results I got were older, despite reading that multithreading has improved much, particularly in C++20.
Thank you in advance!
Edit: As requested, here is a minimal working example. Of course, the full algorithm is more complex, considers the edge cases and enters them also for other applicable nodes (e.g. the result of comparing 5 & 7 also applies for 6 & 7).
// Example.h
#pragma once
#include <iostream>
#include <unordered_map>
#include <thread>
struct MyNode {
int id;
int leftNodeID;
int rightNodeID;
std::shared_ptr<std::unordered_map<int, float>> localCostMap; /* inherited from parent (left/right) nodes */
std::unordered_map<int, std::shared_ptr<std::unordered_map<int, float>>> pooledMaps;
MyNode() : id(0), leftNodeID(0), rightNodeID(0) { setLocalCostMap(); }
MyNode(int _id, int leftID, int rightID) :
id(_id), leftNodeID(leftID), rightNodeID(rightID) { setLocalCostMap(); }
void setLocalCostMap();
float calculateNodeCost(int otherNodeID);
};
// Example.cpp
#include "NodeMapMin.h"
MyNode nodes[8];
void MyNode::setLocalCostMap() {
if (leftNodeID == 0) { // rightNodeID also 0
localCostMap = std::make_shared<std::unordered_map<int, float>>();
}
else { // get map from connectednode if possible
auto poolmap = nodes[leftNodeID].pooledMaps.find(rightNodeID);
if (poolmap == nodes[leftNodeID].pooledMaps.end()) {
localCostMap = std::make_shared<std::unordered_map<int, float>>();
nodes[leftNodeID].pooledMaps.insert({ rightNodeID, localCostMap }); // [1] possible conflict
nodes[rightNodeID].pooledMaps.insert({ leftNodeID, localCostMap }); // [1] possible conflict
}
else { localCostMap = poolmap->second; }
}
}
float MyNode::calculateNodeCost(int otherNodeID) {
if (id > 0) {
std::cout << "calculateNodeCost for " << nodes[id].id << " and " << nodes[otherNodeID].id << std::endl;
}
float costs = -1.0f;
auto mapIterator = localCostMap->find(otherNodeID);
if (mapIterator == localCostMap->end()) {
if (id == otherNodeID) { // same node
std::cout << "return costs for " << id << " and " << otherNodeID << " (same node): " << 0.0f << std::endl;
return 0.0f;
}
else if (leftNodeID == 0 || nodes[otherNodeID].leftNodeID == 0) {
costs = ((float)(id + nodes[otherNodeID].id)) / 2;
std::cout << "calculated costs for " << id << " and " << otherNodeID << " (no connections): " << costs << std::endl;
}
else if (leftNodeID == nodes[otherNodeID].leftNodeID &&
rightNodeID == nodes[otherNodeID].rightNodeID) { // same connected nodes
costs = nodes[leftNodeID].calculateNodeCost(rightNodeID); // [2] possible conflict
std::cout << "return costs for " << id << " and " << otherNodeID << " (same connections): " << costs << std::endl;
return costs;
}
else {
costs = nodes[leftNodeID].calculateNodeCost(otherNodeID) +
nodes[rightNodeID].calculateNodeCost(otherNodeID) +
nodes[id].calculateNodeCost(nodes[otherNodeID].leftNodeID) +
nodes[id].calculateNodeCost(nodes[otherNodeID].rightNodeID); // [2] possible conflict
std::cout << "calculated costs for " << id << " and " << otherNodeID << ": " << costs << std::endl;
}
// [3] possible conflict
localCostMap->insert({ otherNodeID, costs });
nodes[otherNodeID].localCostMap->insert({ id, costs });
}
else {
costs = mapIterator->second;
std::cout << "found costs for " << id << " and " << otherNodeID << ": " << costs << std::endl;
}
return costs;
}
float getNodeCost(int node1, int node2) {
return nodes[node1].calculateNodeCost(node2);
}
int main()
{
nodes[0] = MyNode(0, 0, 0); // should not be used
nodes[1] = MyNode(1, 0, 0);
nodes[2] = MyNode(2, 0, 0);
nodes[3] = MyNode(3, 0, 0);
nodes[4] = MyNode(4, 0, 0);
nodes[5] = MyNode(5, 1, 2);
nodes[6] = MyNode(6, 1, 2);
nodes[7] = MyNode(7, 3, 4);
//getNodeCost(5, 7);
//getNodeCost(6, 7);
std::thread w1(getNodeCost, 5, 7);
std::thread w2(getNodeCost, 6, 7);
w1.join();
w2.join();
std::cout << "done";
}
I commented out the single-thread variant, but you can easily see the difference as the multi-threaded version already has more (unneccessary) comparisons.
As you can see, whenever two "noteworthy" comparisons take place, the result is added to localCostMap, which is normally derived from the connected two nodes. Thus, one insert is necessary for all nodes with these two connections (left & right).
I see at least 3 problematic point:
When initializing the node and inserting the pooled maps for the connected nodes: if two nodes with the same connections were to be added at the same time, they would both want to create and add the maps for the connected nodes. [1]
When calculating the values, another thread might already be doing it, thus leading to unneccessary calculations. [2]
When inserting the results into localCostMap (and by that also to the maps of the connected nodes). [3]
If you already have a std::shared_ptr to one of the inner maps it can be safely used, since, as you explained, once created it is never updated by any execution thread.
However since the outer map is being modified, all access to the outer map must be thread safe. None of the containers in the C++ library are thread safe, it is your responsibility to make them thread safe when needed. This includes threads that only access the outer map, since other execution threads might be modifying it. When something is modified all execution threads are required to use thread-safe access.
This means holding a mutex lock. The best way to avoid bugs that involve thread safety is to make it logically impossible to access something without holding a lock. And the most direct way of enforcing this would be the map to be wrapped as a private class member, with the only way to access it is to call a public method that grabs a mutex lock:
#include <unordered_map>
#include <memory>
#include <mutex>
#include <iostream>
class costs {
std::unordered_map<int, std::shared_ptr<std::unordered_map<int, float>
>> m;
std::mutex mutex;
public:
template<typename T>
auto operator()(T && t)
{
std::unique_lock lock{mutex};
return t(m);
}
};
int main() {
costs c;
// Insert into the map
c([&](auto &m) {
auto values=std::make_shared<std::unordered_map<int, float>>();
(*values)[1]=2;
m.emplace(1, values);
});
// Look things up in a map
auto values=c([]
(auto &m) -> std::shared_ptr<std::unordered_map<int, float>>
{
auto iter=m.find(1);
if (iter == m.end())
return nullptr;
return iter->second;
});
// values can now be used, since nothing will modify it.
return 0;
}
This uses some convenient features of modern C++, but can be implemented all the way back to C++11, with some additional typing.
The only way to access the map is to call the class's () operator which acquires a lock and calls the passed-in callable object, like a lambda, passing to it a reference to the outer map. The lambda can do whatever it wants with it, before returning, at which point the lock gets released.
It is not entirely impossible to defeat this kind of enforced thread safety, but you'll have to go out of your way to access this outer unordered map without holding a lock.
For completeness' sake you may need to implement a second () overload as a const class method.
Note that the second example looks up one of the inner maps and returns it, at which point it's accessible without any locks being held. Presumably nothing would modify it.
You should consider using maps of std::shared_ptr<const std::unordered_map<int, float> instead of std::shared_ptr<std::unordered_map<int, float>. This will let your C++ compiler enforce the fact that, presumably, once created these maps will never be modified. Like I already mentioned: the best way to avoid bugs is to make it logically impossible for them to happen.
I've been reading the cplusplus.com site and trying to make sure that my unordered_set of numbers won't be modified in any way. The site says that the elements of a container are not sorted which is the case with plain set.
The site also says this:
Internally, the elements in the unordered_set are not sorted in any
particular order, but organized into buckets depending on their hash
values to allow for fast access to individual elements directly by
their values.
I have no clue what that means really (can you explain btw.?). Consider following example:
typedef const std::unordered_set<short> set_t;
set_t some_set = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36};
Can I make sure that the above set "some_set" will never be changed and that the numbers will always stay in the same order (because this is the goal here)?
I'm also not planning to insert or remove numbers from the set.
typedef const std::unordered_set<short> set_t;
set_t some_set = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36};
The changing order of the numbers in some_set depends on the operations that you do to some_set. The contents of some_set immediately after creation is not defined, but it probably won't be {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36}. You can see this with a simple demo:
#include <iostream>
#include <unordered_set>
int main() {
typedef const std::unordered_set<short> set_t;
set_t some_set = {1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36};
for (short s : some_set)
std::cout << s << std::endl;
// The order won't change if we don't modify the contents
std::cout << "AGAIN!" << std::endl;
for (short s : some_set)
std::cout << s << std::endl;
// If we put a bunch of stuff in
for (short s = 31; s < 100; s += 4)
some_set.insert(s);
// The elements from *before* the modification are not necessarily in the
// same order as before.
std::cout << "MODIFIED" << std::endl;
for (short s : some_set)
std::cout << s << std::endl;
}
I ran into a strange problem. I have a vector<pair<bool, int>> from which I need to read (and possibly write) only the vector elements for which the boolean value of the pair is true. I am using boost range filter and reverse adaptors to do that.
However, I noticed that the order of the adaptors, ie whether I use reversed | filtered or filtered | reversed produces different results. In fact, when I use filtered | reversed then when I use an iterator to the transformed range to change the boolean value of the pair, then the iterator after the change points to a different vector element. This does not happen when I use reversed | filtered. Below is the code demonstrating the issue. Any ideas as to why this is happening are much appreciated!
#include <boost/range/adaptors.hpp>
#include <vector>
#include <utility>
#include <iostream>
using namespace boost::adaptors;
using container_type = std::vector<std::pair<bool,int>>;
struct to_include {
bool operator()(const std::pair<bool,int>& x) {
return x.first;
}
};
int main() {
container_type container;
/* element0: 1, 1 */
/* element1: 1, 2 */
/* element2: 1, 3 */
for(size_t i=0; i!=3; ++i) container.push_back(std::make_pair(true, i+1));
container_type container_cpy = container;
/* filter and then reverse */
auto fr = container | filtered(to_include()) | reversed;
auto fr_it1 = fr.begin();
auto fr_it2 = std::next(fr_it1);
fr_it2->first = false;
std::cout << "FILTER AND THEN REVERSE\n";
std::cout << fr_it2->first << " " << fr_it2->second << '\n'; /* prints (1,1) instead of (0,2) */
/* reverse and then filter */
auto rf = container_cpy | reversed | filtered(to_include());
auto rf_it1 = rf.begin();
auto rf_it2 = std::next(rf_it1);
rf_it2->first = false;
std::cout << "\nREVERSE AND THEN FILTER\n";
std::cout << rf_it2->first << " " << rf_it2->second << '\n'; /* prints (0,2) */
return 0;
}
This is a subtle issue. The point here is that after you modify the element pointed to by fr_it2, you also implicitly modify fr_it1 because fr is a lazy view on the original range. This means that the transformed filter needs to be recomputed. This is a very non-intuitive property, because for eager STL ranges, modifications through iterators don't modify the iterators themselves, but for lazy ranges this is no longer true!
In fact, if you print the entire fr and rf ranges using "fresh" iterators, you will see that their contents are in fact the same.
fr_it2->first = false;
for (auto e : fr) std::cout << e.first << e.second << ";"; // prints 13;11
...
rf_it2->first = false;
for (auto e : rf) std::cout << e.first << e.second << ";"; // prints 13;11
Live Example 1. So in fact the middle element is indeed deleted!
I think you should not modify elements through iterators into the adapated range, but rather through iterators into your primary container, like this:
auto fr_it1 = container.begin();
...
auto rf_it1 = container_cpy.begin();
Live Example 2.
If you do that, you get consistent results that show "0 2" for both approaches.
Example:
for (vector<string>::reverse_iterator it = myV.rbegin(); it != myV.rend(); ++it)
{
cout << "current value is: " << *it << ", and current position is: " << /* */ << endl;
}
I know I could check how many items there are in the vector, make a counter, and so on. But I wonder if there is a more direct way of checking current index without asserting that I got the length of the vector right.
vector Iterators support difference you can subtract you current iterator it from rbegin.
EDIT
As noted in a comment not all iterators support operator- so std::distance would have to be used. However I would not recommend this as std::distance will cause a liner time performance cost for iterators that are not random access while if you use it - begin() the compiler will tell you that won't work and then you can use distance if you must.
Subtract std::vector<T>::begin() (or rbegin() in your case) from the current iterator. Here's a small example:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> x;
x.push_back(1);
x.push_back(1);
x.push_back(3);
std::cout << "Elements: " << x.end() - x.begin();
std::cout << "R-Elements: " << x.rend() - x.rbegin();
return 0;
}
As pointed out in a really great comment above, std::distance may be an even better choice. std::distance supports random access iterators in constant time, but also supports other categories of iterators in linear time.
Iterators are used to allow generic algorithms to be written that invariant to a choice of a container. I've read in the STL Book that this is great, but may lead to performance drop because sometimes the member functions of a container are optimized for the container and will run faster than generic code that relies on iterators. In this case, if you are dealing with a large vector, you will be calling the std::distance, which although constant is not necessary. If you know that you will be using oly vector for this algorithm, you may recognize that it supports the direct access operator "[]" and write something like this:
#include <vector>
#include <iostream>
using namespace std;
int main ()
{
vector<int> myV;
for (int I = 0; I < 100; ++I)
{
myV.push_back(I);
}
for (int I = 0; I < myV.size(); ++I)
{
cout << "current value is: " << myV[I]
<< ", and current position is: " << I << endl;
}
return 0;
}
In case you are interested in speed, you can always try the different answers proposed here and measure the execution time. It will depend on the vector size probably.
Keep a counter:
for (vector<string>::reverse_iterator it = myV.rbegin(),
int pos = myV.size;
it != myV.rend(),
--pos;
++it)
{
cout << "current value is: " << *it << ", and current position is: " << pos << endl;
}
According to SGI, cplusplus.com, and every other source I've got, the sort() member function of the std::list should not invalidate iterators. However, that doesn't seem to be the case when I run this code (c++11):
#include <list>
#include <chrono>
#include <random>
#include <iostream>
#include "print.hpp"
unsigned int seed = std::chrono::system_clock::now().time_since_epoch().count();
std::default_random_engine generator(seed);
std::uniform_int_distribution<unsigned int> distribution(1, 1000000000);
auto rng = std::bind(distribution, generator);
// C++11 RNG stuff. Basically, rng() now gives some unsigned int [1, 1000000000]
int main() {
unsigned int values(0);
std::cin >> values; // Determine the size of the list
std::list<unsigned int> c;
for (unsigned int n(0); n < values; ++n) {
c.push_front(rng());
}
auto c0(c);
auto it(c.begin()), it0(c0.begin());
for (unsigned int n(0); n < 7; ++n) {
++it; // Offset these iterators so I can print 7 values
++it0;
}
std::cout << "With seed: " << seed << "\n";
std::cout << "Unsorted list: \n";
print(c.begin(), c.end()) << "\n";
print(c.begin(), it) << "\n\n";
auto t0 = std::chrono::steady_clock::now();
c0.sort();
auto d0 = std::chrono::steady_clock::now() - t0;
std::cout << "Sorted list: \n";
print(c0.begin(), c0.end()) << "\n";
print(c0.begin(), it0) << "\n"; // My own print function, given further below
std::cout << "Seconds: " << std::chrono::duration<double>(d0).count() << std::endl;
return 0;
}
In print.hpp:
#include <iostream>
template<class InputIterator>
std::ostream& print(InputIterator begin, const InputIterator& end,
std::ostream& out = std::cout) {
bool first(true);
out << "{";
for (; begin != end; ++begin) {
if (first) {
out << (*begin);
first = false;
} else {
out << ", " << (*begin);
}
}
out << "}";
return out;
}
Sample input/output:
11
With seed: 3454921017
Unsorted list:
{625860546, 672762972, 319409064, 8707580, 317964049, 762505303, 756270868, 249266563, 224065083, 843444019, 523600743}
{625860546, 672762972, 319409064, 8707580, 317964049, 762505303, 756270868}
Sorted list:
{8707580, 224065083, 249266563, 317964049, 319409064, 523600743, 625860546, 672762972, 756270868, 762505303, 843444019}
{8707580, 224065083}
Seconds: 2.7e-05
Everything works as expected, except for the printing. It is supposed to show 7 elements, but instead the actual number is fairly haphazard, provided "value" is set to more than 7. Sometimes it gives none, sometimes it gives 1, sometimes 10, sometimes 7, etc.
So, is there something observably wrong with my code, or does this indicate that g++'s std::list (and std::forward_list) is not standards conforming?
Thanks in advance!
The iterators remain valid and still refer to the same elements of the list, which have been re-ordered.
So I don't think your code does what you think it does. It prints the list from the beginning, to wherever the 7th element ended up after the list was sorted. The number of elements it prints therefore depends on the values in the list, of course.
Consider the following code:
#include <list>
#include <iostream>
int main() {
std::list<int> l;
l.push_back(1);
l.push_back(0);
std::cout << (void*)(&*l.begin()) << "\n";
l.sort();
std::cout << (void*)(&*l.begin()) << "\n";
}
The two address printed differ, showing that (unlike std::sort), std::list::sort has sorted by changing the links between the elements, not by assigning new values to the elements.
I've always assumed that this is mandated (likewise for reverse()). I can't actually find explicit text to say so, but if you look at the description of merge, and consider that the reason for list::sort to exist is presumably because mergesort works nicely with lists, then I think it's "obviously" intended. merge says, "Pointers and references to the moved elements of x now refer to those same elements but as members of *this" (23.3.5.5./23), and the start of the section that includes merge and sort says, "Since lists allow fast insertion and erasing from the middle of a list, certain operations are provided specifically for them" (23.3.5.5/1).