News:

Simutrans.com Portal
Our Simutrans site. You can find everything about Simutrans from here.

Re: While coding C++ how do you usually test your expressions and functions?

Started by sdog, November 25, 2016, 06:24:59 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Ters

When has pulling the rug out from underneath anything ever been a sane thing to do? I thought that was implicitly evil.

sdog

I think some clarification with regard to the const matter would be good. It seems that intended and perceived meaning do not converge yet.

Initially we were talking specifically about constant references. For example let N be a field that includes all valid values for int and y in N, and int a = x; const int &b;. I understand this as a reference where the value of a is mutable but this value can be accessed read-only through b; ie a = y; is possible while b = y; is not, for all y in N\x.
Now, I understand the const attibute in the context of a & reference is to inform the compiler that the latter operation, b += y;, is forbidden. However, in the binary there is no difference if there is a pointer to the address denoted where the value of a is stored, a const reference, a non-const reference or a itself. Is this so far correct?


I should like to discuss this quote in that context, I number sentences for later reference:
Quote
(1) I think it is more of an instruction to the compiler to forbid you from changing the value. (2)(const also predates const&, which affects how the latter works.) (3) However, the programmer can pull the rug out from underneath the compiler by casting away the const-ness and changing the value nonetheless. (4) Even without casting away the const-ness, the value can change, so I'm not sure how many assumptions the compiler can make.
[...]
(5) I do not know of any cases where pass-by-value is turned into pass-by-const-reference, I just can't rule it out.
(1) appears to be consistent with what I think. However, (5) is slightly contradictory, why would the compiler, that creates the machine code bother with instructions to itself? That gets me to the second point of (5) when the compiler can establish that there are no writes to the data structure that would be copied due to a pass-by-value, it will not duplicated the data structure but read from the original address (as if it were passed as pass by const ref). That seems to be for easy cases default behaviour for most compilers, eg one has to turn it off in gcc with several -fno switches. A similar example is return value optimization you mentioned.

Is (2) due to some sloppy naming const int c = 0; appears an entirely different concept than const int& .... The former is what is understood as constant, while the other could be more aptly named read-only reference?

(3)(4) the example that follows doesn't pull the rug out, that seems to be exactly the way a reference ought to work, doesn't it? If the original value changes, the reference ought to reflect this new state, if one needs the original value one would have to copy it, whether as a variable or a constant, in any case without the &. I cannot quite follow why this would be pulling the rug. However, I do not know the const cast you mentioned, and seemed to have in mind. Pulling rugs is bad, and nothing I have to be concerned much at this stage, I suppose.

Example: let arb1, arb2 be arbitrary data structure:

arb2 f (arb1 x)
{
    arb2 y;
    ...   
    //instructions where the state of x is not changed
    ...
    return y;
}
arb2 a = f(b)
[...]


Even if f(x) would not be inlined, the compiler would not duplicate any data structures, a and b would be the only ones addressed.

void f (arb1 &x, arb2 *y)
{
  ...
  // same as above
}
[...]
f( b, a )

would be no more effective, while it is much less readable, cannot be made pure, and offers less chance for optimisation, and is at risk of breaking paralellisation*. Unless the compiler cannot establish that x remmains unperturbed, and does indeed copy

*I've stumbled over 'aliasing' which seems to mean something in this context, I've not read up on it. C++ has a tendency to give concepts often very confusing names, that mean entirely different things in any non C/C++ context. Vector is a particularily nasty example, which is very much unlike a vector (elements can be removed and added) but more like an (effective implementation) of a list. One can only guess that these terms were used because apt terms were already used for other stuff, like ineffective implementations of lists (std::list).



Someone's loud snoring woke me up (05:25 now) and it occurred to me that you might have meant: "what if (a) a concurrent process changes the value while the function is executed or (b) the function itself changes that value somehow. I'll return to that later today.

Ters

First of all const int &a is a reference to a constrant integer. In C++, the references themselves are always constant, in the sense that you can never change the reference to reference something else. This is unlike pointers, which can be pointed elsewhere, unless the pointer itself is constant (int * const a), as well as references in Java (unless final).

(5) The compiler does not instruct itself (nor any other compilers). I don't know where you get that from. The compiler instance compiling the callee can know that the function does not modify the value, but the compiler instance compiling the caller does not necessarily know that, nor can the compiler compiling the callee know if there are other callers elsewhere (unless the function is static). Therefore, the parameters of the function can not be modified from pass-by-value to pass-by-reference. If it did callers that know nothing better than to pass by value, would crash.

As for pulling the rug, that had to do with doing naughty things like

const int c = 1;
int &d = const_cast<int &>(c);
d = 2;
// c may or may not equal 2 now, depending on how the compiler implemented this in machine code

not anything like what you show in (3) and (4).

sdog

Quote from: Ters on January 01, 2017, 10:42:07 AM
First of all const int &a is a reference to a constrant integer. In C++, the references themselves are always constant, in the sense that you can never change the reference to reference something else. This is unlike pointers, which can be pointed elsewhere, unless the pointer itself is constant (int * const a), as well as references in Java (unless final).
The highlighted text would mean I missed the point entirely. Let's test if I confused the syntax and int const& is different from const int&:


[cling]$ int a = 0;
[cling]$ const int& b = a;
[cling]$ int const& c = a;
[cling]$ b
(const int) 0
[cling]$ c
(const int) 0
[cling]$ a = 1
(int) 1
[cling]$ b
(const int) 1
[cling]$ c
(const int) 1

Both b and c seem to reference a, where a is a mutable integer. Checking the actual references:

[cling]$ &a
(int *) 0x7f3614297000
[cling]$ &c
(const int *) 0x7f3614297000


They all go to the same memory address. I suppose a may be changed without pulling the rug? Is this just a misunderstanding, lack of understanding of the terminology, or do I get something fundamentally wrong.

The second part of the quote is clear, here's a test:


// reference without const attribute
[cling]$ int &d =a
(int) 5
[cling]$ &d
(int *) 0x7f3614297000

// and a pointer
[cling]$ int * p = &a
(int *) 0x7f3614297000

[cling]$ int other_a
(int) 0
[cling]$ p = &other_a
(int *) 0x7f3614297030

// attepmt to re-asign non-const reference
[cling]$ &d = &a;
input_line_90:2:5: error: expression is not assignable
&d = &a;

// and const reference
[cling]$ &c = &a
input_line_92:2:5: error: expression is not assignable
&c = &a



Quote
(5) The compiler does not instruct itself (nor any other compilers). I don't know where you get that from.
Well, that seemed implicit from what you wrote above, therefore I asked again, and it cleared as a misunderstanding. However, as above shows there is more to it.


Quote
The compiler instance compiling the callee can know that the function does not modify the value, but the compiler instance compiling the caller does not necessarily know that, nor can the compiler compiling the callee know if there are other callers elsewhere (unless the function is static).
I think here we are touching what I thought of last night and added to the previous message. Let f(int x){...} a function and int a=0; [...] int b = f(a); the function call. Out of my head I can see two cases where the value of a changes while the function is processed: (i) a concurrent process changes a while f(int x) is running. (ii) the function has side effects and changes the global variable a while working with its copy x. Are there more conceivable cases (that leave the rug in place)?

In case (i) the compiler must duplicate a to a and x. If one were to pass by a reference or a pointer one would have unpredictable outcome of the function f as its arguments might change at any time during its runtime.

Case (ii) seems like absolutely terrible coding, and I think that is not just my preference for purity and functional approach. This would also break any other basic optimisation like copy propagation and inlining.

In both cases, (i) and (ii) pass by value is safer, while pass by reference might cause havoc. The compiler would err on the safer side and duplicate data structures.

I've not thought about it earlier, as I simply assumed that this would happen. That was an oversight.

The conclusion seems to be similar though:

Quote
Therefore, the parameters of the function can not be modified from pass-by-value to pass-by-reference. If it did callers that know nothing better than to pass by value, would crash.

As for pulling the rug, that had to do with doing naughty things like

const int c = 1;
int &d = const_cast<int &>(c);
d = 2;
// c may or may not equal 2 now, depending on how the compiler implemented this in machine code

not anything like what you show in (3) and (4).

const_cast from const to mutable (volatile?) seems to be a mindbogglingly awful idea. Why are such things in a standard? It cannot seriously be to enable people to do hackish workarounds to broken APIs Dr Super Good mentioned?


This discussion brought me to two new questions:

If the compiler does cannot properly optimise code, it seems preferable to change the code in such a fashion that the compiler can optimise, rather than optimising it manually.

How to determine (without too much effort) if the compiler optimises the code I'm currently working on? Profiling? For something that specific it seems rather tedious. Purposefully obstructing compiler optimisation attempts and then profiling this as a control sample?

Ters

Quote from: sdog on January 01, 2017, 01:34:41 PM
The highlighted text would mean I missed the point entirely. Let's test if I confused the syntax and int const& is different from const int&:

Both are references to constant integers. With pointers you can have pointers to constants (const int * and, I think, int const *), constant pointers to non-constants (int * const) and constant pointers to constants (const int * const). (This can get really confusing once you deal with pointers to pointers.) References are always constant, that is they can never be changed to refer to something else (except through dirty hacks), but can refer to values that are either constant or not.

Quote from: sdog on January 01, 2017, 01:34:41 PM
They all go to the same memory address. I suppose a may be changed without pulling the rug?

It can be a bit of rug-pulling. It might be mostly on yourself (and possible co-workers), though.

Quote from: sdog on January 01, 2017, 01:34:41 PM
The compiler would err on the safer side and duplicate data structures.

Duplicating data structures is not necessarily safer (this goes for all programming languages with mutable data). In C++, it might even be disallowed if the coder has disabled the copy constructor and assignment operator for the data structure.

Quote from: sdog on January 01, 2017, 01:34:41 PM
const_cast from const to mutable (volatile?) seems to be a mindbogglingly awful idea. Why are such things in a standard? It cannot seriously be to enable people to do hackish workarounds to broken APIs Dr Super Good mentioned?

Yes they are. In fact, they might even be there just for the hacks, broken APIs or not. C is so low level that programmers could get around constness in multiple (I can think of two off the bat, three if simple C casts are considered non-standard) ways anyway, so they might have made a standard way of doing it just so that it is easy to find the places such naughty things are done. C is itself not quite const-correct. Maybe it didn't have the concept originally. strchr is an example in pure C (C++ gets it right, since it supports overloading).

volatile is for various kinds concurrency (between threads, or between software and hardware), and not strictly speaking as the opposite of constant. I can imagine something being both const and volatile, if it is a memory-mapped read-only input device, but I do not know if C actually allows it.

Quote from: sdog on January 01, 2017, 01:34:41 PM
How to determine (without too much effort) if the compiler optimises the code I'm currently working on?

The compiler will only optimize if you tell it to. It will also only do the kinds of optimizations you tell it to. Whether it actually determines to apply a particular optimization can only be found out by looking at the assembly as far as I know.