5

Is the below code well-defined and guaranteed to work as expected?

#include <iostream>
#include <string>

using std::string;

int main() {
  string const a[2][2][2] = {
      "1", "2", "3", "4", "5", "6", "7", "8",
  };

  auto constexpr sz = sizeof(a) / sizeof(string);
  auto const p = reinterpret_cast<string const*>(&a);
  for (std::size_t i = 0; i < sz; ++i) std::cout << p[i] << '\n';
}

To my knowledge, since we cast it to a pointer, no out-of-bounds rules apply as long as we access valid memory, and thusly, this is OK. But I couldn't be sure.

Note: I am not asking if the array is contiguous (it is), I am asking if casting a multi-dimensional array to a flat pointer, and using it to access all elements is well defined (not UB).

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
  • This has to be a duplicate, but I can't find a good candidate right now. – 500 - Internal Server Error Feb 08 '21 at 11:27
  • @500-InternalServerError I know right! I did search for that duplicate before asking, but couldn't find. – Aykhan Hagverdili Feb 08 '21 at 11:27
  • 1
    @JHBonarius no it doesn't. I am aware that the array is contiguous. The problem is that we are using the address of one of the arrays to access elements of the other, and I would like to know if this well defined. It obviously works, because the array is contiguous. – Aykhan Hagverdili Feb 08 '21 at 11:40
  • 2
    @JHBonarius I don't think that answers it. Even though it is contigous, afaik, you aren't allowed to access the fourth element through `a[0][3]` even though we _know_ it to be the same as `a[1][0]`. – Ted Lyngmo Feb 08 '21 at 11:43
  • Ok, but the OP of that question seems to be doing the C equivalent of what you are doing... – JHBonarius Feb 08 '21 at 11:44
  • 1
    @JHBonarius I don't know the rules for C. Perhaps it's well defined in C. Also, that question isn't a language-lawyer question (not even for C) so I don't think it would be a good duplicate. – Ted Lyngmo Feb 08 '21 at 11:46
  • 2
    @JHBonarius C and C++ are very different things, particularly around shady things such as unions, type punning and aliasing. It's an interesting question from that it particularly asks for language-lawyering (i.e.g what standard sections allow or prohibit this); so whilst there may be some viable duplicates out there ("this just works" / "this is just illegal"), I think we should take care not to close this one unless the dupe answer using references to the standard (and interpretations of it, if needed). – dfrib Feb 08 '21 at 11:49
  • 1
    ... I'm wonder what strict aliasing says about aliasing `string const (*)[3]` via `string const*`. [basic.life]? – dfrib Feb 08 '21 at 11:50
  • 1
    Pointer arithmetic like `p[i]` is only defined for pointers into an array (where a stand-alone object is treated as a one-element array). If you want to claim that `p` is not a pointer to an element of any array, then the only valid indexes are 0 and 1 (the latter producing a past-the-end pointer). – Igor Tandetnik Feb 08 '21 at 12:29
  • 1
    Also, I suspect `reinterpret_cast(&a)` exhibits undefined behavior (or rather, using the resulting pointer does). **[basic.compound]** has a note specifically pointing out that "An array object and its first element are not pointer-interconvertible, even though they have the same address." – Igor Tandetnik Feb 08 '21 at 12:31
  • @IgorTandetnik I just found [that note](https://eel.is/c++draft/basic.compound#note-4) myself. It seems to be the same in C++23 so far. I wonder if doing the cleaner `auto p = a[0][0];` would make it ok? My guess is that it doesn't. – Ted Lyngmo Feb 08 '21 at 12:34
  • 1
    My reading is that `p` now points into an array `string[2]`, and so valid indexes are 0, 1 and 2. As far as I can tell, the C++ standard doesn't in fact allow walking over a multi-dimensional array as if it were a large flat one-dimensional array. – Igor Tandetnik Feb 08 '21 at 12:44
  • @IgorTandetnik Yes, the `p` in my example would be `std::is_same_v` and I agree. `p+3` would be the _one past the end_ pointer of the inner array and dereferencing it is probably UB. – Ted Lyngmo Feb 08 '21 at 13:20
  • @IgorTandetnik what if I do `auto p = (string const*)(char const*)&a;` ? Would that fix all the pointer-conversion problems? – Aykhan Hagverdili Feb 08 '21 at 14:10
  • That's just `reinterpret_cast` by another name. I don't think it'll help any. Recall that `reinterpret_cast` between two unrelated pointer types is equivalent to `static_cast` followed by `static_cast` to the destination type, which is similar to what you propose. – Igor Tandetnik Feb 08 '21 at 16:34
  • It is UB because of https://timsong-cpp.github.io/cppwp/n4861/expr.add#6. I think there already are similar question on this site. – Language Lawyer Feb 08 '21 at 16:40
  • https://stackoverflow.com/questions/62676422/is-reinterpret-castcharmytypeptr-assumed-to-point-to-an-array I think this kinda answers this question, except that it doesn't go into "pointer-interconvertible" details about the cast – Language Lawyer Feb 08 '21 at 16:52
  • @LanguageLawyer The comment of mine that you are citing was in response to `auto p = a[0][0];` proposal. That one seems pretty clear; `a[0][0]` is an array `string[2]`, which decays to a pointer to its first element, which is then assigned to `p`. It's equivalent to `auto p = &a[0][0][0];`. `p` is now a pointer to an element of `string[2]` – Igor Tandetnik Feb 08 '21 at 16:58
  • @LanguageLawyer how is the part of the standard you're quoting relevant? It seems to be about polymorphic types and related polymorphism. – Aykhan Hagverdili Feb 08 '21 at 16:58
  • @IgorTandetnik Ah, ok. I'll remove my comments. – Language Lawyer Feb 08 '21 at 17:02
  • @AyxanHaqverdili do you know what Notes are for and what does «In particular» mean? If you want I can give a link which doesn't highlight the Note https://timsong-cpp.github.io/cppwp/n4861/expr.add#6.sentence-1 – Language Lawyer Feb 08 '21 at 17:02
  • @lan I do know what 'in particular' means. It just seems irrelevant in this context. We do have an array of `std::string` objects _at that memory address_, and `std::string` is similar to `std::string`. Please write an answer instead of debating in the comments. – Aykhan Hagverdili Feb 08 '21 at 17:06
  • _We do have an array of std::string objects at that memory address_ But `p` doesn't point to an object of type `std::string` (or `const std::string`), so you step into [expr.add]/6 – Language Lawyer Feb 08 '21 at 17:08
  • *"But p doesn't point to an object of type std::string"* Why not? The type of `p` is literally `string const*` and there is a `string` object at that address. – Aykhan Hagverdili Feb 08 '21 at 17:09
  • _Why not?_ Because the standard says so. _The type of p is literally string const* and there is a string object at that address_ It is not enough for `p` to point to an object of type `(const) string`. – Language Lawyer Feb 08 '21 at 17:11
  • @lan I don't see where standard says that. There is no pointer arithmetic going on in the line where `p` is defined. Please write an answer so other people can join the discussion and vote up/down. – Aykhan Hagverdili Feb 08 '21 at 17:15
  • _I don't see where standard says that_ This https://stackoverflow.com/a/62341088/ has details about pointer-interconvertibility and casts. `string const` is not pointer-interconvertible with `string const[2][2][2]`, so the cast doesn't change the pointer value, so the value after the cast is the same as the value of `&a` — «pointer to object `a`» (which is of type `string const[2][2][2]`). – Language Lawyer Feb 08 '21 at 17:18
  • @lan I find the other answer in your link more convincing honestly. – Aykhan Hagverdili Feb 08 '21 at 17:46
  • @dfrib _I'm wonder what strict aliasing says about aliasing `string const (*)[3]` via `string const*`_ Nothing. _[basic.life]?_ Same. – Language Lawyer Feb 08 '21 at 20:09
  • @Davis yes, that's an exact duplicate, but there are many conflicting answers even just under that question. – Aykhan Hagverdili Feb 09 '21 at 05:42
  • @AyxanHaqverdili: Well, at the moment the first is non-committal and the second is correct, if that helps. – Davis Herring Feb 09 '21 at 07:58
  • @DavisHerring how is this question a dup of the current dup target if this one has UB not because of [expr.add]/4, as described in the correct answer. – Language Lawyer Feb 09 '21 at 16:54
  • 1
    @LanguageLawyer: I consider it a separate mistake in the question that it trips over pointer-interconvertibility before it gets to the indexing. We can obviously replace the cast here with just `**a`. – Davis Herring Feb 09 '21 at 18:27

0 Answers0