1. 5
  1.  

  2. 5

    One of those changes is that R0’s algorithm std::relocate_at(T* source, T* dest) is joined by T std::relocate(T* source). This new algorithm std::relocate takes the object pointed to by source and relocates it into the return slot, giving you a prvalue. You can use this prvalue in combination with the superconstructing super elider (2018-05-17) to emplace the relocated object directly into a container (Godbolt):

    This… wow. I’m sure this makes sense to someone, but not me.

    (Not a criticism of the author! I’m not the target audience! Not even necessarily a criticism of C++—I’m sure you could write equally baffling-to-an-outsider statements about languages I’m familiar with.)

    1. 8

      I’m sure this makes sense to someone, but not me.

      Let’s have a go…

      One of those changes is that R0’s algorithm std::relocate_at(T* source, T* dest) is joined by T std::relocate(T* source).

      There was a thing that did a move from one argument to another. There’s now a new version that moves from the argument to the return value. This takes advantage of guaranteed copy elision in recent versions of C++. This is a feature that, in turn, takes advantage of an aspect of most ABIs: if you return a large thing, then this is lowered to the caller allocating space and passing you a pointer for you to fill in. With guaranteed copy elision, the language says that, if you locally allocate a thing with automatic storage and then return it then the compiler promises to actually use the space allocated by the caller.

      This new algorithm std::relocate takes the object pointed to by source and relocates it into the return slot, giving you a prvalue.

      This does the thing I said above. In most programming languages, you have two kinds of values: l-values are things that go on the left of an assignment, r-values are things that go on the right. Informally, r-values describe a value, l-values describe a location of a value. C++11 also has a bunch of other categories. A pr-value is simply a ‘pure’ rvalue, an r-value that cannot be used as an l-value.

      You can use this prvalue in combination with the superconstructing super elider (2018-05-17) to emplace the relocated object directly into a container (Godbolt):

      If you use this with a more aggressive form of copy elision, this lets you store the return value of this function in a container without needing any additional copies. This means that if you pass the result of std::relocate to an emplace function then it will guarantee that it does a single copy from the source to the storage allocated by the container and destroys the original object.

      1. 2

        Wow, thanks—that makes a lot of sense.

    2. 3

      I’m generally an advocate of C++ but rather than “cute”, I found this implementation (of casting memcpy to a different type of function, which “works” based on knowledge of underlying ABI) kind of horrific. Surely this is the sort of thing that should be done with a compiler builtin, rather than relying on specific semantics for code that definitely invokes undefined behaviour.

      1. 2

        I agree that this code is very nonportable, but this is a compiler / standard library fork. (As a proof-of-concept for a proposal to change the standard.)

      2. 2

        Not sure what to think of the cute, ABI-specific impl there. Wouldn’t an optimizer be able to achieve that?

        1. 1

          I guess that’s part of the plan, and part of what makes it cute. For a language extension, the main thing is that it can be done and is worth extending the language for. Which I guess takes a lot of elaboration to figure out. Maybe it was so cute on Itanium because it didn’t work out on x86_64 or something?

          1. 5

            The name “Itanium ABI” is misleading. It’s an open source ABI that was originally created for Itanium, but is now used by GCC and Clang by default on all platforms, including x86_64.

            https://itanium-cxx-abi.github.io/cxx-abi/abi.html

        2. 1

          The title needs “…on the Itanium architecture” appended to it. Does anyone even care about Itanium anymore?

          1. 3

            Itanium is perhaps the coolest architecture of all time.

            1. 3

              It’s talking about the Itanium ABI, which is used (instantiated, with modifications) on a heap of architectures, including x86 and x86-64.

              1. 2

                Interesting, I didn’t realize the Itanium ABI was supported on other processors.

                1. 9

                  The Itanium had a few features that, taken together, made it very difficult to implement a traditional setjmp / longjmp that just spilled and restored a bunch of registers (including the stack pointer and return address). As a result, HP developed a generic stack unwinding infrastructure that allows setjmp, stack walking, and C++ exceptions to be implemented on top of a common set of routines. They also specified a two-layer exception ABI, one that was generic to any language and one that was specific to C++ and interoperable with any other language that shared the low-level ABI (so a C++ catch (...) could catch and correctly destroy a foreign exception, even if the C++ compiler had no knowledge of the other language and so that any language’s exceptions could propagate through any other language’s stack frames). This document also included a few other things that are important for the C++ ABI, such as name mangling.

                  Prior to this, to my knowledge, no platform had a stable defined C++ ABI. This is one of the big reasons that a lot of people learned to hate C++ in the ’90s: two different C++ compilers could interoperate via extern "C" functions and POD structs, but nothing else. On Windows, they could interoperate via COM, but the MSVC++ compiler would change the C++ ABI regularly on Windows, GCC and other compilers would change their C++ ABI slightly less often elsewhere.

                  The Itanium ABI, in contrast, allowed any pair of C++ compilers for Itanium to interoperate. Importantly, almost nothing in it was actually specific to Itanium (a few uses of int64_t where it meant intptr_t, but only in a handful of places). It did not talk about register usage or low-level structure layout, for example, but deferred these things to the C ABI and defined everything relative to a C ABI. GCC fairly quickly adopted this ABI for all architectures. GCC 3.0 (2001) onwards have all used this ABI, as has clang for almost all non-Windows platforms. 32-bit Arm has a slight tweak to the ABI (guard words are 32 bits, not 64, which makes sense given that only two of the bits in the word are actually used, and Arm has a slightly different encoding for pointers-to-members because the low bit that the Itanium ABI steals is used to differentiate between T32 and A32 modes on Arm).

                  1. 2

                    To be clear, this is specifically the Itanium C++ ABI - It’s concerned with details such as when values are passed on stack vs in registers, how class objects are passed/returned, how vtables are structured, and also a bunch of details about what runtime support looks like. So you can take the non-processor specific parts out and re-purpose it for another architecture pretty easily, and that’s exactly what has been done.