"Automatic" fill

Started by
22 comments, last by alvaro 13 years, 5 months ago
Then if it is undefined behavior, the generate command doesn't solve it neither, and I did a test on it also. I haven't got so far to really understand this: "Lack of sequence points" , but probably it will show itself later.
Advertisement
The undefined behavior comes from using the decrement operator in your loop on a variable that you access again in the function call. Calling the std::generate() would presumably not suffer from that problem since you would need neither the loop nor the decrement. How exactly did you test it?
Sorry for the delay.
The generate works good the other way around (functions and iterators). But what I meaned with this specific problem is that it cannot be used like this:

vector<int> vec(5);
for(int f = 1; f < 5; ++f)
{
generate( vec.begin()+(--f), vec.end(), f );
}

Because as you said it access the scoped value multiple times.





std::fill is designed to fill the range with the same value, not different ones. But if you want to force the behaviour for some reason then it is possible.

class Increment{public:	Increment(){		x = 1;	}	operator int() const{		return x++;	}	mutable int x;	};int main(){	std::vector<int> v(5);	std::fill(v.begin(),v.end(),Increment());


It's necessary to mark Increment's cast operator as const because std::fill accepts it's value as a const reference. I have no idea if this code is guaranteed to function correctly under the standard or not. The standard mightn't forbid Increment being assigned more times than necessary, in which case the code would break on that implementation.

This is frivulous use of the algorithm functions because all you really need to assign the values 1-5 is:

std::vector<int> v(5);
for(int i=0;i<5;i++)
v = i+1;

Or even:

int vals[] = {1,2,3,4,5};
std::vector<int> v(vals,vals+5);
Regarding sequence points, a sequence point is a point in the code where the side-effects of the previous code has been evaluated. C++ does not specify in which order function arguments should be evaluated, so a compiler is free to evaluate a functions arguments in whichever order it wants. When you try to do
fill(vec.begin() + (--f), vec.end(), f);
you therefore have no way of telling what the value of the last f is, since it may or may not have been decremented.

And you still seem to be confused about what algorithms such as fill and generate does. These algorithms work on a range defined by a begin and end iterator. If you were to execute the follow code:
vector<int> vec(5);for(int f = 1; f <= 5; ++f){  fill(vec.begin() + (f - 1), vec.end(), f);}

this is what will happen:
fill(vec.begin() + 0, vec.end(), 1) => vec = {1, 1, 1, 1, 1}fill(vec.begin() + 1, vec.end(), 2) => vec = {1, 2, 2, 2, 2}fill(vec.begin() + 3, vec.end(), 3) => vec = {1, 2, 3, 3, 3}fill(vec.begin() + 4, vec.end(), 4) => vec = {1, 2, 3, 4, 4}fill(vec.begin() + 5, vec.end(), 5) => vec = {1, 2, 3, 4, 5}

So if you fix the problems in your code that leads to undefined behaviour it will indeed work. But, you will have used (n^2 + n)/2 = 15 (with n = 5) assignments, instead of just assigning each elements once (you're using a quadratic algorithm instead of a linear one). If you use a vector with 100 elements you will instead have to do 5050 assignments instead of 100. I'm sure that you realize that that's not a good way of solving the problem.

So if you wish to assign each element in a vector some value, use either a for-loop or an algorithm such as generate, but not both.
Quote:Original post by jden
Then if it is undefined behavior, the generate command doesn't solve it neither, and I did a test on it also. I haven't got so far to really understand this: "Lack of sequence points" , but probably it will show itself later.


The entire point of using std::fill, std::generate, and everything else of that sort is to avoid writing your own loop. They replace loops in your code.

I will walk you through it step by step.

We will call std::generate, one time, to put every desired value into the vector. std::generate will call a 'predicate', several times - once for each value in the range - to determine what value to use.

So, first we make a predicate that will return values in ascending order. The neat, reusable way to do this is to use a class which overloads the operator(). This operator lets us treat objects of the class as if they were functions.

We make an class that stores a counter, and implements its operator() by incrementing the counter and returning the value. Since we want the values in the vector to start with 1, we will start the counter at 0. That way, it will increase to 1 the first time it gets called by std::generate().

So, our predicate looks like this:

class counter {  int value;  public:  counter(): value(0) {}  int operator()() { ++value; return value; }};


Nice and simple, right? And we can probably find other uses for it later, too.

Now we use it with std::generate:

vector<int> vec(5);// We create a counter, without naming it, and pass it to std::generate.// std::generate calls it once for each element, each time increasing the value.std::generate(vec.begin(), vec.end(), counter());


Notice that we did not write 'for' or 'while' or anything like that anywhere. That is because looping is the job of the standard library function.

std::fill is a simpler, but less flexible utility: instead of using a predicate, you just give it a value. That means the same value will be written to the entire range, but you don't have to write a predicate.



As for the 'sequence points' thing, you absolutely must understand this to write proper C++. Please put down everything else you are doing and learn this first. The problem with sequence points has nothing to do with how you call std::fill or std::generate. It has everything to do with how you use the variable 'f'.

You may not write "foo(something + (--f), another_thing, f)", no matter what 'foo', 'something' and 'another_thing' actually are. There is no guarantee of ordering between the parameters. This line of code does not mean "calculate something + --f, then calculate another_thing, then calculate f, then pass it all to foo". It means "calculate these three things that I wrote, in whatever order you like, and pass them to foo (in the order I wrote them)". '--f' changes the value of f. Therefore it absolutely matters whether this is done before or after 'f' is calculated.

What you really meant, instead of '--f', was 'f - 1'. That does not change the value of f. But then there is still no point to using a standard library algorithm, if you are going to write the loop anyway.
Quote:Original post by taz0010
std::fill is designed to fill the range with the same value, not different ones. But if you want to force the behaviour for some reason then it is possible.... I have no idea if this code is guaranteed to function correctly under the standard or not.


Don't say you can do something and then admit that you don't know that it's valid.

And don't abuse implicit casting and 'mutable' to try to make std::fill do what you want, when std::generate does what you want.
Thanks Zahlman.
Ok, so how to insert a sequence point manually?

Wiki says that sequence points is created with some operators and function calls etc? And forget about the algorithms as they really have no directly connections(?) to undefined behaviors.

This one is the same:
vector<int> vec(5);
for(int f = 1; f < 5; ++f)
{
vec[--f] = f; // Like: vec[0] = 1;.. up to five
}
Quote:Original post by jden
Ok, so how to insert a sequence point manually?
You don't. Instead you write code that does not present with a lack of sequence points.
Quote:
vector<int> vec(5);for(int f = 1; f < 5; ++f){	vec[--f] = f; // Like: vec[0] = 1;.. up to five}
The reason this doesn't work has very little to do with sequence points, and very much to do with the *logic* being incorrect.

--f does not mean f-1, as you seem to believe. It means f = f-1.

And since you use ++f to increment once per loop, the decrement is going to cancel that out, so you are going to loop infinitely over the first element in the array.

Tristam MacDonald. Ex-BigTech Software Engineer. Future farmer. [https://trist.am]

I mean, it's not like writing this: f = --f; So I don't see the logic.I only know that everywhere --/++ is used it's going to change the actual value included with that.

This topic is closed to new replies.

Advertisement