5

I am looking for a way to map types to numeric values at compile time, ideally without using a hash as proposed in this answer.

Since pointers can be constexpr, I tried this:

struct Base{};
template<typename T> struct instance : public Base{};

template<typename T>
constexpr auto type_instance = instance<T>{};

template<typename T>
constexpr const Base* type_pointer = &type_instance<T>;

constexpr auto x = type_pointer<int> - type_pointer<float>; // not a constant expression

Both gcc and clang reject this code because type_pointer<int> - type_pointer<float> is not a constant expression, see here, for instance.

Why though?

I can understand that the difference between both values is not going to be stable from one compilation to the next, but within one compilation, it should be constexpr, IMHO.

Rumburak
  • 3,416
  • 16
  • 27
  • Is `auto x` declared and initialised in a static/global context or is it inside a function? – Dai Jan 04 '20 at 15:24

1 Answers1

11

Subtraction of two non-null pointers which do not point into the same array or to the same object (including one-past-the-array/object) is undefined behavior, see [expr.add] (in particular paragraph 5 and 7) of the C++17 standard (final draft).

Expressions which would have core undefined behavior[1] if evaluated are never constant expressions, see [expr.const]/2.6.

Therefore type_pointer<int> - type_pointer<float> cannot be a constant expression, because the two pointers are to unrelated objects.

Since type_pointer<int> - type_pointer<float> is not a constant expression, it cannot be used to initialize a constexpr variable such as

constexpr auto x = type_pointer<int> - type_pointer<float>;

Trying to use a non-constant expression as initializer to a constexpr variable makes the program ill-formed and requires the compiler to print a diagnostic message. This is what the error message you are seeing is.

Basically compilers are required to diagnose core undefined behavior when it appears in purely compile-time contexts.

You can see that there will be no error if the pointers are to the same object, e.g.:

constexpr auto x = type_pointer<int> - type_pointer<int>;

Here the subtraction is well-defined and the initializer is a constant expression. So the code will compile (and won't have undefined behavior). x will have a well-defined value of 0.


Be aware that if you make x non-constexpr the compiler won't be required to diagnose the undefined behavior and to print a diagnostic message anymore. It is therefore likely to compile.

Subtracting unrelated pointers is still undefined behavior though, not only unspecified behavior. Therefore you will loose any guarantee on what the resulting program will do. It does not only mean that you will get different values for x in each compilation/execution of the code.


[1] Core undefined behavior here refers to undefined behavior in the core language, in contrast with undefined behavior due to use of the standard library. It is unspecified whether undefined behavior as specified for the library causes an (otherwise constant) expression to not be a constant expression, see final sentence before the example in [expr.const]/2.

walnut
  • 21,629
  • 4
  • 23
  • 59
  • This seems like a bit of a cheating answer. ;) Technically correct, but it seems unlikely that the practical cause of this error is the UB (which a program _has_, or rather _hasn't_; you can't "cause" it) - perhaps the question should give an example where the pointers are into an array, instead. I bet that has the same problem. – Lightness Races in Orbit Jan 04 '20 at 15:29
  • 1
    @LightnessRacesBY-SA3.0 - Why is it cheating? The UB is literally what must be diagnosed here https://timsong-cpp.github.io/cppwp/n4659/expr.const#2.6 – StoryTeller - Unslander Monica Jan 04 '20 at 15:31
  • What if the `float*` is pointing to the same memory (integer object) as the `int*`? Then they're both pointing to the same object, and we are not accessing that integer from the `float*` since we never dereferenced it. Is this still undefined behavior? – Aykhan Hagverdili Jan 04 '20 at 15:31
  • 1
    @LightnessRacesBY-SA3.0 A simple modification showing that this is the error is to subtract pointers to the same object instead, see https://godbolt.org/z/2MpFvX. I am not sure what you are trying to get at. – walnut Jan 04 '20 at 15:35
  • @Ayxan I am not sure what you are referring to. There are no `int*` or `float*` in OP's code. I have added an example that *does* work to my answer. Is that what you mean? – walnut Jan 04 '20 at 15:37
  • @walnut Oh wow, okay then yeah. I wasn't trying to "get at" anything beyond literally what I said... but I was wrong. :) – Lightness Races in Orbit Jan 04 '20 at 15:38
  • (I suggest quoting expr.const/2.6 in the answer as this was news to me and could be news to others...) – Lightness Races in Orbit Jan 04 '20 at 15:38
  • @LightnessRacesBY-SA3.0 I put in a link to the section. – walnut Jan 04 '20 at 15:49
  • @LightnessRacesBY-SA3.0 I have added an explanation. – walnut Jan 04 '20 at 16:15
  • Actually _"will not be ill-formed anymore"_ is wrong. The ill-formedness category will simply change to "ill-formed, no diagnostic required", which implies undefined behavior. – Ruslan Jan 04 '20 at 17:36
  • Great answer! Took me a while to find [expr.add](http://eel.is/c++draft/expr.add#5) regarding the undefined behavior in pointer subtraction you mentioned. Maybe you could add that link, too? – Rumburak Jan 04 '20 at 17:41
  • 1
    @Rumburak I added a link to the C++17 version of that section, though the wording in the current draft that you linked is much clearer. – walnut Jan 04 '20 at 17:47