4

Pointers to class member variables can be compared and the results depend on the declaration sequence. See this in the spec For instance this example in compiler explorer is valid and returns true (1):

struct A {
    int a0 = 1;
    int a1 = 2;
};

consteval int foo()
{
    A a;
    int* p1 = &a.a0;
    int* p2 = &a.a1;
    return p2 > p1;
}

int main()
{
    return foo();
}

So one would expect that p2-p1 would return the distance, in int objects, between the pointers. And it does at runtime. compiler explorer

struct A {
    int a0 = 1;
    int a1 = 2;
};

int foo()
{
    A a;
    int* p1 = &a.a0;
    int* p2 = &a.a1;
    return p2 - p1;
}

int main()
{
    return foo();
}

But not at compile time. GCC, CLANG, and MSVC all behave differently. GCC compiles and returns the expected value, CLANG complains UB, and MSVC compiles but returns 4, not 1, the distance in bytes! Compiler Explorer

CLANG: note: subtracted pointers are not elements of the same array

Which is correct?

Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
doug
  • 3,840
  • 1
  • 14
  • 18
  • 1
    The highlighted spec requires them to be greater, but doesn't say anything about placement – Vivick Aug 21 '23 at 21:11
  • afaik structs can have varying amounts of padding added to their entries, so like Vivick said, the MSVC compiler might just be adding a different padding – Patrick Aug 21 '23 at 21:14
  • @eerorika at runtime sure. But at compile time with no library calls where UB may be allowed without a diagnostic? – doug Aug 21 '23 at 21:25
  • 1
    "So one would expect that p2-p1 would return the distance," - I don't believe you can conclude that – Jesper Juhl Aug 21 '23 at 21:45
  • @eerorika My understanding is that some, but not all, UB requires a diagnostic if executed at compile time in a compile time const function. For instance signed int overflow. Is that not true? UB related to library code may or may not result in a diagnostic. – doug Aug 21 '23 at 22:29
  • @doug Good point. In such case UB would have to be diagnosed. – eerorika Aug 21 '23 at 22:34

1 Answers1

15

You may not subtract pointers to objects unless they are elements of the same array. It is undefined behavior so you can get different results.

if P and Q point to, respectively, array elements i and j of the same array object x, the expression P - Q has the value i − j.

Otherwise, the behavior is undefined.

http://eel.is/c++draft/expr.add#5.2

clang is correct in labeling it as undefined behavior, gcc and msvc are correct in doing whatever they want because it is undefined behavior. If the function is consteval then only clang is correct, the others should emit a diagnostic

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • Yeah, that was my thinking too. I found it odd that the the pointers could be compared legally even though they pointed to different objects. Only case I know of that the first is legal but not the second so thought I'd test it. – doug Aug 21 '23 at 21:22
  • 4
    Technically, all the compilers are correct if behaviour is undefined, since the standard doesn't impose any requirements, and undefined behaviour is not required to be diagnosed at all. It is probably fairer to say that clang gives a diagnostic that identifies the actual concern in a way that is more comprehensible to a human i.e. the difference between compilers is in quality of implementation, not correctness. – Peter Aug 22 '23 at 00:48
  • @Peter yes, well put, I edited the last line – Ryan Haining Aug 22 '23 at 17:20
  • @Peter Compilers must fail/issue a diagnostic when evaluating a constant expression containing UB (excluding UB that occurs in library calls) during compile. That is the reason for `consteval` in foo. It forces eval at compile time. See: [Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp].](https://eel.is/c++draft/defns.undefined) – doug Aug 23 '23 at 03:16
  • @doug I'm reading [expr.const](https://eel.is/c++draft/expr.const) and I don't think it says that every expression in an immediate function is a constant expression. Can you point me to the relevant line? Clang and gcc both reject [this code](https://wandbox.org/permlink/wjND7shkML6RMXiT) – Ryan Haining Aug 23 '23 at 05:20
  • @doug The sentence you quote is part of a note. Unlike normative text in the standard, notes are not binding i.e. they don't delimit what an *implementation* is required to do or allowed to not do. The relevant normative text immediately before that note defines UB as "behavior for which this document imposes no requirements" (no requirement to issue a diagnostic is a subset of that). Practically, notes include guidance or advice (e.g. in this case, advising the author of code to avoid introducing UB during evaluation of a constant expression) not requirements for compilers. – Peter Aug 23 '23 at 09:33
  • @RyanHaining You can't initialize an array with a non const anywhere. Same as you can't do this: `int a{1}; int array[a];` – doug Aug 23 '23 at 19:20
  • 1
    @Peter UB generally allows the compiler and program to do anything or nothing. But there is an exception. A lot of UB behavior prohibits expressions from being core constant. And when that happens in a compile time evaluation, they can't be const evaluated and a diagnostic is required. See the rather detailed discussion of what and when UB is required to be detected [here](https://stackoverflow.com/questions/72183253/is-it-guaranteed-that-all-forms-of-undefined-behavior-are-caught-when-evaluating) A lot of good background in the comments too. – doug Aug 23 '23 at 19:25
  • @doug of course. But, if `A a{}` were a constant expression, then shouldn't you be able to use `a.a0` for the size of an array? – Ryan Haining Aug 23 '23 at 20:08
  • @RyanHaining Well, in a function evaluated at compile time you can legally have stuff like `int i{1}; int *p=&i; p++;` All regular, legal stuff. And p is certainly not const within the function. But try to do this: ``int i{1}; int *p=&i; p+2-1;` and suddenly one has UB and a compile time error because the grouping creates `p+2` before subtracting 1. Instant UB. Works fine at runtime of course. Still UB. – doug Aug 23 '23 at 21:46
  • @doug I'm responding to "Evaluation of a constant expression ([expr.const]) never exhibits behavior explicitly specified as undefined in [intro] through [cpp]" by saying that it doesn't appear that `A a{}` counts as a constant expression even though you have `consteval` on `foo`, I'm not convinced that means every expression in `foo` counts as a constant expression for the purposes of that rule. – Ryan Haining Aug 23 '23 at 22:37
  • @RyanHaining I agree. I perhaps worded it wrong. In evaluating a constexpr function at compile time it is not required that the function's code use only const values. Only that the end result is const and no UB occur. Requiring everything in the function to use const variables would make it pretty useless. GCC is well known for ignoring some constraints here. For instance it happily accepts std::reinterpret` which is not allowed at all. Here's an example where CLANG and MSVC flag but GCC doesn't when the comment is removed: https://godbolt.org/z/abcc84Yh7 – doug Aug 23 '23 at 22:58
  • @doug "Only that the end result is const and no UB occur" no UB where? We're talking about `consteval` functions not `constexpr`, right? – Ryan Haining Aug 24 '23 at 17:49
  • @RyanHaining Right, `consteval` functions differ from ``constexpr` functions only that for the latter to require any UB diagnostic they must be evaluated at compile time and that often isn't the case. My example code would not require compile time eval were the function `constexpr` – doug Aug 24 '23 at 21:07
  • @doug That last point is where I am maybe disagreeing. Does expr.const say that any expression in an immediate function is a constant expression? I don't think it does (though I may be wrong), so I don't think the spec requires a UB diagnostic. – Ryan Haining Aug 24 '23 at 21:37
  • @RyanHaining Any `consteval` function is only evaluated at compile time. The code isn't even included in the compiled executable whereas a `constexpr` function will be if it's called at runtime. No doubt the standard could use a lot more clarity. The question I linked to earlier in response to Peter has perhaps the best discussion of all this I've seen. – doug Aug 25 '23 at 04:29
  • @doug I posted [a question](https://stackoverflow.com/q/76979172/1013719) aimed at my potential misunderstanding here – Ryan Haining Aug 25 '23 at 16:57
  • @RyanHaining Thanks for following this up. Seems there is a great deal of confusion around this. I feel a lot better using `constexpr` function techniques. – doug Aug 25 '23 at 21:28