0

I was watching a C++Con video on YouTube found here.

I became interested in these new concepts. I tried to implement the code snippets from slides 27 and 29 from the time stamps @23:00 - @26:30. There is a subtle difference in my code where I added the operator()() to my_function class in order to use it within the auto range loop within main().

Also, I had to modify the less_than within the sort_by() function call by using its operator()() in order for Compiler Explorer to compile the code.

Here is my version of the code that does compile:

#include <iostream>
#include <vector>
#include <algorithm>

struct less_than {
    template<typename T, typename U>
    bool operator()(this less_than, const T& lhs, const U& rhs) {
        return lhs < rhs;
    }
};

struct my_vector : std::vector<int> {
    using std::vector<int>::vector;

    auto sorted_by(this my_vector self, auto comp) -> my_vector {
        std::sort(self.begin(), self.end(), comp);
        return self;
    }

    my_vector& operator()() {
        return *this;
    }
};

int main() {
    my_vector{3,1,4,1,5,9,2,6,5}.sorted_by(less_than());

    for (auto v : my_vector()) {
        std::cout << v << " ";
    }

    return 0;
}

Here is my link to Compiler Explorer to see the actual compiled code, assembly as well as the executed output.

The code does compile and executes. The vector within main() does contain the values from its initializer list constructor that can be seen within the assembly. However it appears that nothing is being printed to the standard output, or it is being constructed, sorted and then destroyed from the same line of c++ execution and its going out of scope before referencing it within the auto range loop.

The topic at this point in the video is about automatically deducing the this pointer to simplify build patterns to reduce the complexity of CRTP and the concept here is to introduce By-Value this: Move Chains.

Yes this is experimental and may change before the entire C++23 language standard is implemented and released.

I'm just looking for both insight and clarity to make sure that I'm understanding what's happening within my code according to Compiler Explorer, the topic of this talk, and what the future may bring for newer language features.

Is my assumption of why I'm not getting an output correct? Does it pertain to object lifetime, visibility and or going out of scope?

Francis Cugler
  • 7,788
  • 2
  • 28
  • 59
  • 2
    I am not sure why you are expecting anything else. `my_vector{3,1,4,1,5,9,2,6,5}.sorted_by(less_than())` is a prvalue/temporary vector and `my_vector()` is another prvalue/temporary vector. Maybe your intent is `auto vec = my_vector{3,1,4,1,5,9,2,6,5}.sorted_by(less_than());` and then `for (auto v : vec)`? This has nothing to do with the new features. If these were just usual member functions without deduced `this`, then the same would apply. – user17732522 Jun 20 '22 at 23:18
  • @user17732522 Beat me to it! But I'll leave my answer there anyway. – Paul Sanders Jun 20 '22 at 23:20
  • @user17732522 I was just trying to print the vector after being sorted by using this newer pattern matching feature by deducing the this pointer through pass by value semantics. So maybe using `auto vec = ...` might have worked in my situation... I've been away from C++ for a little over a year now so I'm a bit rusty... I overlooked the fact that I didn't declare an actual variable object of `my_vector` type that has a specified lifetime within main. This has answered my question... There is a provided answer, but wasn't what I was looking for. – Francis Cugler Jun 20 '22 at 23:43
  • 1
    @FrancisCugler I think you somehow got the wrong idea about this new feature from the video. This is not pattern matching and there is no global state or state of the `my_vector` class here or anything like that. The only thing this new feature does is to allow one to implement `sorted_by` efficiently without having to write two overloads. Prior to C++23 you had to write `sorted_by` once for a `const &`-qualified overload and once for a `&&`-overload to make use of move semantics where possible. Nothing is new regarding the behavior in `main`. – user17732522 Jun 20 '22 at 23:48
  • @user17732522 I understand that there is nothing new. I wouldn't say pattern matching directly, more like simplifying the use of CRTP by automatically deducing the this pointer from passing it explicitly. This should greatly increase the simplicity of language syntax for a more concise and readable code base with less of a chance to introduce error prone bugs. As I stated in a comment above, I had overlooked the fact that I didn't create an object of type my_vector; I was just calling it's initializer list constructor and throwing the object away. That's why I was asking for clarity. – Francis Cugler Jun 20 '22 at 23:53

1 Answers1

2

I can't say I know a lot about C++23, but I don't think your problem is to do with that per-se. This:

for (auto v : my_vector()) ...

default-constructs an (empty, temporary) vector and then runs a range for loop on it, and MSVC is evidently smart enough to see that this is effectively a no-op and throws the whole thing away.

But if you do this:

for (auto v : my_vector{3,1,4,1,5,9,2,6,5}.sorted_by(less_than())) ...

then what looks to me to be reasonable code is generated. Pity we can't run it. Widen the panes on the right-hand side a little bit to see the program output!

Godbolt link

Paul Sanders
  • 24,133
  • 4
  • 26
  • 48
  • "_Pity we can't run it._": You are doing exactly that. Widen the panes on the right-hand side a bit. – user17732522 Jun 20 '22 at 23:21
  • 2
    @user17732522 Oh, he's made that possible now (didn't used to be, IIRC). Cool. – Paul Sanders Jun 20 '22 at 23:24
  • So, it appears that my assumptions were close in that the object was created and destroyed, yet through compiler optimization it just discarded the whole thing. So this would in effect be a complete temporary and would have to be used "in-place" so to speak as opposed to an actual object with a limited lifetime. – Francis Cugler Jun 20 '22 at 23:40
  • Not quite sure what you're getting at in that last bit there, but, as a bit of a generalisation, a temporary is an object without a name that is constructed, used in some way, and then destroyed, usually (but not always) all in the same statement. And yes, from looking at the code, the compiler just threw that whole loop away. (Whether the compiler actually constructs anything in such cases tends to depend on the optimisation level used. I didn't look too closely to see what MSVC actually got up to there; like you I just noticed that there were no `operator<<` calls generated.) – Paul Sanders Jun 20 '22 at 23:46
  • @PaulSanders I appreciate your feedback, however, there was a comment to my question that actually answered my question. I've been away from programming in C++ for a little over a year now and I had overlooked the fact that I had created a temporary instead of creating an actual object type of `my_vector` on the stack within main to be used within the ranged-for-loop. However, your answer itself is not wrong as it is a possible way of using it. So I did give an upvote, but did not accept as it wasn't the exact answer I was looking for even though it would still print the correct output. – Francis Cugler Jun 20 '22 at 23:58
  • 1
    @Francis That's OK, I really don't mind. SO is [supposed to be] a meritocracy and you must vote as you see fit. – Paul Sanders Jun 21 '22 at 05:50
  • 1
    @user17732522 Didn't used to be possible for MSVC, I mean. [Most of] the other compilers were always runnable. I like it when you pick me up on these details, BTW. I learn a lot. – Paul Sanders Jun 21 '22 at 05:52
  • 1
    @FrancisCugler This answer is pretty much saying the same I would have said. Whether you store `my_vector{3,1,4,1,5,9,2,6,5}.sorted_by(less_than())` into a variable, e.g. `auto vec = ...;` as I suggested, or use it as the range-expression in a range-for loop doesn't matter. The range-for loop stores a variable referring to the range-expression (although not by value, but by reference to a (lifetime-extended) temporary, i.e. `auto&& vec = ...;`). In any case the issue was just that `vector()` had no relation to the other vector, however they are stored or what their lifetimes are. – user17732522 Jun 21 '22 at 06:52
  • 1
    @user17732522 Oooh, those lifetime-extended temporaries :) – Paul Sanders Jun 21 '22 at 06:59