I have no multiple inheritance and I must scream


  • Banned

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    Notice that it is whole one release AFTER they introduced lambda and auto. It really looks like they couldn't foresee where auto would be most useful.

    It's not just that there are too many features. It's that they add new ones with little regard of how they interact with the rest of the system.

    Why do you think the standardization process takes so long?

    Because that's how bureaucracies work?

    Because that's exactly what they don't do.

    If they actually analyze the interaction of features, they do a really bad job of it. That, or C++ has too many features for proper analysis to be possible.

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.

    template<typename A, typename B, typename F>
    std::vector<B> map(std::vector<A> v, F f)
    {
    std::vector<B> r;
    for (auto i: v) r.emplace_back(f(i));
    return r;
    }
    
    std::vector<Foo> v;
    // fill in some instances of Foo
    map(v, [](auto i) { std::cout << i << std::endl; return i; });
    

    How to rewrite the code above to avoid any copies being made? Would anything change if map() was made non-template?



  • @ben_lubar said in I have no multiple inheritance and I must scream:

    You could make AuthenticatedAPIRequest an attribute and check for a token in the handler that parses the request.

    Then I'd need to use reflection to determine whether the request object contained the Token field, though. Or am I missing something?

    Again, people: KISS. Adding reflection for something like this is just stupid complexity.



  • @dfdub said in I have no multiple inheritance and I must scream:

    We're getting seriously OT here (sorry blakeyrat!), but one last reply:

    No biggie, I already wrote the code. But it is a really boring conversation, so maybe you should knock it off and go grab a Slurpee or something.


  • Discourse touched me in a no-no place

    @masonwheeler said in I have no multiple inheritance and I must scream:

    No, let's instead pile layers of architecture atop layers of architecture!

    Keep on piling them on and the whole thing will become self-supporting, with the lower layers compacting into a sort of dense neutronium plating of architecture!!!


  • Discourse touched me in a no-no place

    @blakeyrat said in I have no multiple inheritance and I must scream:

    JSON (unlike XML) has no concept of a "fixed schema"

    There is a schema language for JSON. It's not that commonly used but there's an implementation of the validator for C# so it should be fairly easy to integrate for you guys. We use JSON Schema a fair bit at work to ensure that we're not doing insane stupid shit in our data serialisation code (since that has to work with third-party stuff) and for checking that the input to our deserialisers is sane (since we work with third-party stuff 😉).

    [EDIT}: Well and truly :hanzo:d :(


  • Discourse touched me in a no-no place

    @jbert said in I have no multiple inheritance and I must scream:

    what's to say that a field might not change its meaning along the way?

    NO!

    Do not do this! For the love of all that you hold dear and holy, do not do this.


  • Considered Harmful

    @sockpuppet7 said in I have no multiple inheritance and I must scream:

    @pie_flavor if you want a fixed schema, why not a binary format? the point of wasting bits with these silly tags is supposed to make things schemaless

    Human editability. Also, things like Message pack and NBT are schemaless.


  • Considered Harmful

    @gąska said in I have no multiple inheritance and I must scream:

    things you can theoretically achieve in C++ are a strict superset of what you can do in C#.

    can you generate template signatures at runtime with reflection?


  • Fake News

    @dkf said in I have no multiple inheritance and I must scream:

    @jbert said in I have no multiple inheritance and I must scream:

    what's to say that a field might not change its meaning along the way?

    NO!

    Do not do this! For the love of all that you hold dear and holy, do not do this.

    You're yelling at me as if I'm thinking it's a good idea, but just be realistic.

    When someone new gets to add a new API call they may very well spot one of your base classes, see that it has the property names they need and inherit from it, then interpret it differently in most of the API implementation and inadvertently pass the POCO into an existing function accepting the base class.

    If you require everyone to build their own POCOs they should at least stop to think of how they need to call those existing functions because they will have to write their own code copying fields into the parameter classes.


  • Discourse touched me in a no-no place



  • Shouldn't authorization tokens be sent as a header and not as part of the request?



  • @alexmedia Why don't you tell me.

    EDIT: with less snark, the reason I did that is because one of our clients is Excel, and I believe (but am not 100% sure) Excel API requests can't edit headers.



  • @blakeyrat

    Ah, I see where you're coming from. If you can't manipulate HTTP request headers you'll have to leave that stuff elsewhere. I've had to deal with similar limitations in the past.

    After much headbanging against a wall (thanks, IE) I ended up stuffing custom headers into the query string as X-Override-Headername=xyz123 key-value pairs which were parsed on the server side by a Web API HTTP message handler.

    It wasn't exactly pretty, but it helped with keeping somewhat of a RESTful API.


  • Banned

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    things you can theoretically achieve in C++ are a strict superset of what you can do in C#.

    can you generate template signatures at runtime with reflection?

    ...okay, reflections are rather bad in C++. But I wonder why would you ever need this particular feature. It sounds like either runtime codegen, which can be done in C++ via compiler libraries (ie. compilers that are accessible through libraries), or a way to achieve some goal rather than the goal itself.


  • Considered Harmful

    @gąska Just general reflection stuff. Calling methods at runtime.



  • @pie_flavor Isn't that what "normal" code does all the time?
    What makes reflection so useful and different than just making an object of a certain type and calling its methods?


  • Considered Harmful

    @djls45 :wtf: Have you never used any sort of framework or anything?



  • @pie_flavor Sure. The Java code at work uses it, but it seems like most uses are to find and use global objects. There is a kinda neat trick where we can add code to the system and it has some code that will compile and use the added code on the fly, but that doesn't seem to be "reflection" AIUI. Maybe I'm wrong?

    I just don't see how it can be a general (as opposed to specialized) purpose to investigate what methods/properties/etc. some object has and be able to change them out in the code. Either you know what methods you can call, and in what contexts (so you write the code to call them) or you don't, and so why are you trying to use that unknown object anyways?


  • Considered Harmful

    @djls45 said in I have no multiple inheritance and I must scream:

    There is a kinda neat trick where we can add code to the system and it has some code that will compile and use the added code on the fly, but that doesn't seem to be "reflection" AIUI. Maybe I'm wrong?

    Yes, that is reflection.
    If you want to call a constructor of a given class automatically, that's reflection.
    If you want to automatically extract textual configuration into a class structure, that's reflection.
    If you want to load code at runtime, that's reflection.
    If you want to do literally anything with annotations, that's reflection.
    If you want to figure out if a given Class extends another class, or if an object is an instance of it, that's reflection.
    There are a ton of uses for it. I think this is a Chesterton's Fence sort of thing here.



  • @pie_flavor said in I have no multiple inheritance and I must scream:

    @djls45 said in I have no multiple inheritance and I must scream:

    There is a kinda neat trick where we can add code to the system and it has some code that will compile and use the added code on the fly, but that doesn't seem to be "reflection" AIUI. Maybe I'm wrong?

    Yes, that is reflection.

    What's being "reflected" then?

    If you want to call a constructor of a given class automatically, that's reflection.

    When instantiating a new object? How does that differ from "regular" instantiation?

    If you want to automatically extract textual configuration into a class structure, that's reflection.

    But what makes that different than just reading the configuration into/with a constructor/initializer?

    If you want to load code at runtime, that's reflection.

    I already asked above what's being "reflected" here.

    If you want to do literally anything with annotations, that's reflection.

    Aren't annotations basically compiler hints? How does that fit with runtime activity?

    If you want to figure out if a given Class extends another class, or if an object is an instance of it, that's reflection.

    And you wouldn't know that from the program's/data's structure?
    And so is RTTI the same as (or does it overlap) reflection?

    There are a ton of uses for it. I think this is a Chesterton's Fence sort of thing here.

    And those uses can usually be done another, seemingly simpler (to me, at least), way.


  • Considered Harmful

    @djls45 said in I have no multiple inheritance and I must scream:

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @djls45 said in I have no multiple inheritance and I must scream:

    There is a kinda neat trick where we can add code to the system and it has some code that will compile and use the added code on the fly, but that doesn't seem to be "reflection" AIUI. Maybe I'm wrong?

    Yes, that is reflection.

    What's being "reflected" then?

    Reflection is used to know what parts of the newly loaded code to call.

    If you want to call a constructor of a given class automatically, that's reflection.

    When instantiating a new object? How does that differ from "regular" instantiation?

    'Scuse me. Given Class, not class.

    If you want to automatically extract textual configuration into a class structure, that's reflection.

    But what makes that different than just reading the configuration into/with a constructor/initializer?

    Because the code doing the parsing and mapping does not know what types it's dealing with or what fields it needs to be assigning to. It takes the Class and calls its constructor through reflection. It goes through each key in the configuration, finds a field in that Class with the same name, and assigns the one to the other.
    As an example, this is an example config, this is the class that gets written to hold the values, and this ends up being the loading code.

    If you want to load code at runtime, that's reflection.

    I already asked above what's being "reflected" here.

    If the exact signature of the code was not present at compile-time, then reflection must be used to call it.

    If you want to do literally anything with annotations, that's reflection.

    Aren't annotations basically compiler hints? How does that fit with runtime activity?

    So you haven't used any sort of a framework before. That makes a lot more sense.
    Annotations are not compiler hints. There are a couple of built-in annotations that are compiler hints, but reflection can be used to discover annotations at runtime. For example, Sponge does event handlers like this:

    @Listener
    public void onJoin(ClientConnectionEvent.Join event) {
    

    A class containing methods like this gets registered to the EventManager. When an event gets posted, all public methods that have a @Listener annotation and that event or one of its supertypes as their only parameter get called with that event object.

    If you want to figure out if a given Class extends another class, or if an object is an instance of it, that's reflection.

    And you wouldn't know that from the program's/data's structure?
    And so is RTTI the same as (or does it overlap) reflection?

    When I say 'figure out', I mean at runtime. Reflection provides access to the structure, among other things.

    There are a ton of uses for it. I think this is a Chesterton's Fence sort of thing here.

    And those uses can usually be done another, seemingly simpler (to me, at least), way.

    How would you do the event listener system in another, simpler way? The one I mentioned can handle multiple arbitrary event types in one class without any extra runtime cost, and you don't have to explicitly specify what events it's going to receive anywhere except the method signature. You can also add extra method parameters if you use cause filter annotations such as @First or @Root on the parameters, which retrieve objects from the event's cause or return immediately if they're not present.

    Hell, for that matter, how would you handle the loading system in the first place? My main class is annotated @Plugin. All the plugin information is passed as parameters to that annotation. Sponge knows by that annotation that it's a plugin class and that it should be constructed. Any plugin-specific resources I need from Sponge, I just put as a constructor parameter, which gets automatically passed, or as an annotated field, which gets automatically assigned. How would you do that without reflection?



  • @pie_flavor Ah, okay. So it's reflecting the data model in the code structure, and vice versa. I guess that could make it simpler for some things, since it avoids a bunch of boilerplate code to match things up. I suppose (coming from a C/C++ [≤11] background) that I just prefer not having "hidden" code to do something. I just didn't know that that was called reflection. All the stuff I've seen in reflection tutorials and such just never seemed applicable to anything beyond academics.

    The program that I use/code for at work uses the Hibernate, JBoss, and Adobe Flex frameworks. I don't develop the program itself (that's another team's job), but I do write the add-in "custom code" for the various instances that we have for clients' projects. And we have run into problems with stuff that I think could have been avoided if we had more granular control than the automatic stuff that reflection provides.


  • Banned

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @gąska Just general reflection stuff. Calling methods at runtime.

    Calling methods at runtime can be done in every language that has methods and runtime. And if you mean dynamically calling unknown methods with known names on ubknown types - again, I'd like to hear what's the use case.


  • Considered Harmful

    @djls45

    And reflection doesn't provide automatic stuff. Reflection provides the tools to make it. How granular the control is and how automatic the result is entirely depends on the code using reflection.


  • Considered Harmful

    @gąska said in I have no multiple inheritance and I must scream:

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @gąska Just general reflection stuff. Calling methods at runtime.

    Calling methods at runtime can be done in every language that has methods and runtime. And if you mean dynamically calling unknown methods with known names on ubknown types - again, I'd like to hear what's the use case.

    I repeat again my question about how you'd implement that event system or plugin-loading system without reflection.



  • @dkf said in I have no multiple inheritance and I must scream:

    @masonwheeler said in I have no multiple inheritance and I must scream:

    No, let's instead pile layers of architecture atop layers of architecture!

    Keep on piling them on and the whole thing will become self-supporting, with the lower layers compacting into a sort of dense neutronium plating of architecture!!!

    Well, the lower levels of such architectures tend to be carcinogenic, or at the very least feel like they are.


  • Banned

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @djls45 said in I have no multiple inheritance and I must scream:

    @pie_flavor said in I have no multiple inheritance and I must scream:

    @djls45 said in I have no multiple inheritance and I must scream:

    There is a kinda neat trick where we can add code to the system and it has some code that will compile and use the added code on the fly, but that doesn't seem to be "reflection" AIUI. Maybe I'm wrong?

    Yes, that is reflection.

    What's being "reflected" then?

    Reflection is used to know what parts of the newly loaded code to call.

    Or you could load DLL and find appropriate function. You can even generate those functions on the fly with JIT compiler!

    If you want to call a constructor of a given class automatically, that's reflection.

    When instantiating a new object? How does that differ from "regular" instantiation?

    'Scuse me. Given Class, not class.

    But what does this make possible that's otherwise impossible to do? If a DLL exposes previously agreed on interface, you can do pretty much all the things you can do with reflection. It's fugly compared to reflections, but it doesn't really let you do anything new - it's the same old shtick, just in a nicer package. Compare to C++'s constexpr, which actually makes it possible what was previously impossible - compile-time evaluation of complex expressions.

    If you want to automatically extract textual configuration into a class structure, that's reflection.

    But what makes that different than just reading the configuration into/with a constructor/initializer?

    Because the code doing the parsing and mapping does not know what types it's dealing with or what fields it needs to be assigning to.

    Unless you specify it somehow. Again, nicer packaging.

    If you want to load code at runtime, that's reflection.

    I already asked above what's being "reflected" here.

    If the exact signature of the code was not present at compile-time, then reflection must be used to call it.

    Or you could cast a pointers into a function with previously known signature and call it that way.

    If you want to do literally anything with annotations, that's reflection.

    Aren't annotations basically compiler hints? How does that fit with runtime activity?

    So you haven't used any sort of a framework before. That makes a lot more sense.
    Annotations are not compiler hints. There are a couple of built-in annotations that are compiler hints, but reflection can be used to discover annotations at runtime.

    It's not like you can't do that with bare DLLs and symbol querying. It won't be nearly as nice, but see above.

    If you want to figure out if a given Class extends another class, or if an object is an instance of it, that's reflection.

    And you wouldn't know that from the program's/data's structure?
    And so is RTTI the same as (or does it overlap) reflection?

    When I say 'figure out', I mean at runtime.

    RTTI is runtime feature of C++, and it lets you dynamically determine if a dynamic object is derived from some static superclass. Not to be confused with RAII.

    There are a ton of uses for it. I think this is a Chesterton's Fence sort of thing here.

    And those uses can usually be done another, seemingly simpler (to me, at least), way.

    How would you do the event listener system in another, simpler way?

    Another? Plenty ways to do it. Simpler? Yes, it's hard to beat reflections on this front. But the point isn't that C++ ways are superior to reflections (they're not). The point is that whatever you can do with reflections, you can also do without reflections - with 100x more effort, but still.

    The one I mentioned can handle multiple arbitrary event types in one class without any extra runtime cost

    Runtime cost of reflections is big. It's just not big enough to matter in most cases.

    Hell, for that matter, how would you handle the loading system in the first place? My main class is annotated @Plugin. All the plugin information is passed as parameters to that annotation. Sponge knows by that annotation that it's a plugin class and that it should be constructed. Any plugin-specific resources I need from Sponge, I just put as a constructor parameter, which gets automatically passed, or as an annotated field, which gets automatically assigned. How would you do that without reflection?

    On Windows:

    struct PluginData{ ... };
    typedef const char * const * (*getPluginNamesType)();
    typedef size_t (*getPluginCountType)();
    typedef const PluginData* (*getPluginDataType)(const char* name);
    
    for (auto&& libName: libNames) {
        auto lib = LoadLibrary(libName.c_str());
        auto pluginNames = ((getPluginNamesType) GetProcAddress(lib, "getPluginNames"))();
        auto pluginCount = ((getPluginCountType) GetProcAddress(lib, "getPluginCount"))();
        auto getPluginData = (getPluginDataType) GetProcAddress(lib, "getPluginData");
        for (size_t i = 0; i < pluginCount; ++i) {
            pluginsData[pluginNames[i]] = *getPluginData(pluginNames[i]);
        }
    }
    

    Something like that. Not as pretty as reflections, but still gets the job done.


  • Considered Harmful

    @gąska said in I have no multiple inheritance and I must scream:

    Runtime cost of reflections is big. It's just not big enough to matter in most cases.

    Only when you're reflecting constantly. When the listener gets registered, it gets wrapped in a much more sensible object structure that gets generated based on the annotations. No reflection actually happens when events are called.


  • Banned

    @pie_flavor as if I didn't know. Of course it costs nothing to not use a thing, and you can stop using the thing once you don't need it anymore.



  • @pie_flavor said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    Runtime cost of reflections is big. It's just not big enough to matter in most cases.

    Only when you're reflecting constantly. When the listener gets registered, it gets wrapped in a much more sensible object structure that gets generated based on the annotations. No reflection actually happens when events are called.

    Write a static analyzer for a language.

    Now write a static analyzer for that same language, but support reflection.

    Now imagine your compiler trying to optimize your code.


  • Discourse touched me in a no-no place

    @gąska said in I have no multiple inheritance and I must scream:

    it's the same old shtick, just in a nicer package

    Virtually everything we do in computing is that. C++ also has reflection-like capabilities, in that it has RTTI (when not compiled out). All reflection does is say “What is this thing I have really? What fields does it have? What methods can I call? What arguments do those methods take?” and lets you then access the fields and call the methods. Nothing more. All the other things that people are talking about is stuff like runtime code generation (which can be mixed with reflection to do neat stuff, but is actually something else). The accesses/calls might be completely unsafe — just grabbing the bits or calling the method with whatever arguments you provide — or mediated by a layer that ensures that whatever you do is comparatively safe (e.g., by ensuring that the arguments you provide actually match up those expected and that you're obeying the language's visibility rules).

    Annotations are basically an augment to this: they let you attach extra custom metadata to a class, field or method. They can mean all sorts of things. My favourites are @PostConstruct and @PreDestroy which let me get callbacks as part of an object's extended containerised lifecycle model, but that's only one possible application. Another is to provide extra information to allow for easy serialisation (to and from XML or JSON) and mapping (to a database table). I've seen annotations used to control nullability of types, to declare that a method must be an implementation of another, to control injection, to (help) build mappings of commands, etc. There's a whole load of options.

    C++ by and large doesn't use this stuff, so far as I can see. It doesn't really go in for the sort of frameworks that are associated with this sort of metadata usage, and instead goes for systems where virtually everything is known at compile time down to the last detail, on the assumption that this is how you make faster systems. (Hand in hand with that is the large-scale rejection of RTTI and execution systems with possible runtime failures. And C++ is absolutely nowhere in many key computing areas as a consequence…)

    Runtime cost of reflections is big.

    In comparison to what?


  • Discourse touched me in a no-no place

    @ben_lubar said in I have no multiple inheritance and I must scream:

    Write a static analyzer for a language.
    Now write a static analyzer for that same language, but support reflection.
    Now imagine your compiler trying to optimize your code.

    Now work out how much that matters.


  • Banned

    @dkf said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    it's the same old shtick, just in a nicer package

    Virtually everything we do in computing is that.

    My point exactly.

    C++ by and large doesn't use this stuff, so far as I can see. It doesn't really go in for the sort of frameworks that are associated with this sort of metadata usage, and instead goes for systems where virtually everything is known at compile time down to the last detail, on the assumption that this is how you make faster systems.

    Not quite. It has more to do with compiling to native code and support for runtime-less applications than just doing things compile-time. Also, most of C++ was designed back in the 90s, when the tradeoffs were different.

    Runtime cost of reflections is big.

    In comparison to what?

    In comparison to calling a method of a statically known type (or from statically known vtable). A couple orders of magnitude. But as I said earlier, it doesn't matter in most cases.



  • @dkf said in I have no multiple inheritance and I must scream:

    and instead goes for systems where virtually everything is known at compile time down to the last detail, on the assumption that this is how you make faster systems.

    In respect to reflection and C++, the idea is to expose information in compile time to enable library implementations of runtime-reflection (and, no, this doesn't exist yet). The idea is that you shouldn't pay for what you don't use -- so there should be no runtime overhead (space or time) for users that don't want to use the reflection. AFAIK this is (was) the main reason RTTI was disabled (and occasionally reimplemented manually instead), though it's been a while I've encountered non-legacy code doing this.

    I've seen discussion about including annotations (via [[foo]]) in the compile-time reflection system, and I think there is at least one clang branch that experiments with it (also for use as a external code-generation step, I think).


  • Discourse touched me in a no-no place

    @cvi said in I have no multiple inheritance and I must scream:

    The idea is that you shouldn't pay for what you don't use

    Unfortunately, one of the problems with C++ is that what you actually pay for what you do use is sometimes surprisingly high by comparison with what you pay using C, Java or C#. Got the benchmarks to prove it too (and no, I'm not sharing them)…



  • @gąska said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    Notice that it is whole one release AFTER they introduced lambda and auto. It really looks like they couldn't foresee where auto would be most useful.

    Or maybe they needed some more time to define the interaction of both features? Because that's the #1 reason for delays in the C++ standardization process - exactly what you claim isn't happening.

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.

    template<typename A, typename B, typename F>
    std::vector<B> map(std::vector<A> v, F f)
    {
    std::vector<B> r;
    for (auto i: v) r.emplace_back(f(i));
    return r;
    }
    
    std::vector<Foo> v;
    // fill in some instances of Foo
    map(v, [](auto i) { std::cout << i << std::endl; return i; });
    

    How to rewrite the code above to avoid any copies being made? Would anything change if map() was made non-template?

    I just had some time, so here's my solution (complete with test code):

    #include <iostream>
    #include <vector>
    
    template<typename E, typename F>
    auto map(std::vector<E>& input, F f) -> std::vector<decltype(f(input[0]))>
    {
        std::vector<decltype(f(input[0]))> result;
        for (auto& element : input) {
            result.emplace_back(f(element));
        }
        return result;
    }
    
    struct Foo
    {
        private:
            int value;
            
        public:
            Foo(int value) {
                this->value = value;
            }
            
            Foo(const Foo& other) {
                std::cout << "Copy constructor" << std::endl;
                this->value = other.value;
            }
            
            Foo(Foo&& other) noexcept {
                std::cout << "Move constructor" << std::endl;
                this->value = other.value;
            }
            
            friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
                os << foo.value;
                return os;
            }
    };
    
    int main()
    {
        auto test = std::vector<Foo>{};
        test.emplace_back(3);
        test.emplace_back(2);
        test.emplace_back(1);
        auto test2 = map(test, [](auto& i) { std::cout << i << std::endl; return std::move(i); });
        return test2.size();
    }
    

    Not a single copy constructor will be called if you compile this with a C++17 compiler. And the only ugly part is the fact that you have to write decltype twice.

    (The flaws in your snippet were also quite obvious. It seems like you deliberately tried to create as many copies as possible.)



  • @dkf said in I have no multiple inheritance and I must scream:

    @ben_lubar said in I have no multiple inheritance and I must scream:

    Write a static analyzer for a language.
    Now write a static analyzer for that same language, but support reflection.
    Now imagine your compiler trying to optimize your code.

    Now work out how much that matters.

    If your compiler can't tell when there are bugs in your code, it becomes the programmer's problem, and that's the opposite of why we have computers.


  • Banned

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    Notice that it is whole one release AFTER they introduced lambda and auto. It really looks like they couldn't foresee where auto would be most useful.

    Or maybe they needed some more time to define the interaction of both features? Because that's the #1 reason for delays in the C++ standardization process - exactly what you claim isn't happening.

    I was trolling a bit there. I know that the commission is full of great language designers and they think everything through very thoroughly and that's why it takes so much time. But the very reason it takes so much time is because they're adding too many features that affect too many things. C# is well designed too, but features are introduced at much faster pace, and they don't have accidental usability problems at their intersections. Meanwhile, we're nearing third decade of C++ guys trying to figure out modules.

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.

    template<typename A, typename B, typename F>
    std::vector<B> map(std::vector<A> v, F f)
    {
    std::vector<B> r;
    for (auto i: v) r.emplace_back(f(i));
    return r;
    }
    
    std::vector<Foo> v;
    // fill in some instances of Foo
    map(v, [](auto i) { std::cout << i << std::endl; return i; });
    

    How to rewrite the code above to avoid any copies being made? Would anything change if map() was made non-template?

    I just had some time, so here's my solution:

    #include <iostream>
    #include <vector>
    
    struct Foo
    {
        private:
            int value;
            
        public:
            Foo(int value) {
                this->value = value;
            }
            
            Foo(const Foo& other) {
                std::cout << "Copy constructor" << std::endl;
                this->value = other.value;
            }
            
            Foo(Foo&& other) noexcept {
                std::cout << "Move constructor" << std::endl;
                this->value = other.value;
            }
            
            friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
                os << foo.value;
                return os;
            }
    };
    
    template<typename E, typename F>
    auto map(std::vector<E>& input, F f) -> std::vector<decltype(f(input[0]))>
    {
        std::vector<decltype(f(input[0]))> result;
        for (auto& element : input) {
            result.emplace_back(f(element));
        }
        return result;
    }
    
    int main()
    {
        auto test = std::vector<Foo>{};
        test.emplace_back(3);
        test.emplace_back(2);
        test.emplace_back(1);
        auto test2 = map(test, [](auto& i) { std::cout << i << std::endl; return std::move(i); });
        return test2.size();
    }
    

    Not a single copy constructor will be called if you compile this with a C++17 compiler. And the only ugly part is the fact that you have to write decltype twice.

    I have a few questions about this solution:

    • Why is input passed by non-const reference? AFAIK it makes it impossible to bind to temporaries, so wherever I use this map, I have to make sure I have an lvalue there by assigning my intermediate results to local variables (and if I forget it, it all compiles fine but creates memory access violation). Am I missing something?
    • Why is i passed by non-const reference? Will it work with const reference?
    • Is it important for the lambda to return rvalue reference? What if it returned a value? What if it returned rvalue reference when it is expected to return a value? Are there any downsides to making each and every function in the entire program return rvalue reference instead of value?
    • Shouldn't you have f(std::forward(element)) instead of f(element)? Would it change anything?
    • If I made a function std::vector<float> f(std::vector<char>& c) { return map(map(c, charToInt), intToFloat); }, would it still be non-copy? If not, what changes do I have to do?
    • What would change if map() was made non-template that accepts std::function?
    • How would the code above behave in C++14? What changes would you make then?

    I wish I could answer those questions on my own. But I tried and tried, and the exact meaning of move semantics still escapes me.

    @dfdub said in I have no multiple inheritance and I must scream:

    (The flaws in your snippet were also quite obvious. It seems like you deliberately tried to create as many copies as possible.)

    I wrote them in the most natural way, like I would write them in any other language (including Rust, which also has value types and move semantics). I focused on making the code as clean as possible, instead of trying (and failing) to make it performant with my very limited knowledge about how move semantics work.



  • @gąska said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    @gąska said in I have no multiple inheritance and I must scream:

    they also introduced auto keyword that lets you skip that, but didn't let you use auto in lambda.

    Notice that it is whole one release AFTER they introduced lambda and auto. It really looks like they couldn't foresee where auto would be most useful.

    Or maybe they needed some more time to define the interaction of both features? Because that's the #1 reason for delays in the C++ standardization process - exactly what you claim isn't happening.

    I was trolling a bit there.

    Ah, OK. I guess I fell for it.

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.

    template<typename A, typename B, typename F>
    std::vector<B> map(std::vector<A> v, F f)
    {
    std::vector<B> r;
    for (auto i: v) r.emplace_back(f(i));
    return r;
    }
    
    std::vector<Foo> v;
    // fill in some instances of Foo
    map(v, [](auto i) { std::cout << i << std::endl; return i; });
    

    How to rewrite the code above to avoid any copies being made? Would anything change if map() was made non-template?

    I just had some time, so here's my solution:

    #include <iostream>
    #include <vector>
    
    struct Foo
    {
        private:
            int value;
            
        public:
            Foo(int value) {
                this->value = value;
            }
            
            Foo(const Foo& other) {
                std::cout << "Copy constructor" << std::endl;
                this->value = other.value;
            }
            
            Foo(Foo&& other) noexcept {
                std::cout << "Move constructor" << std::endl;
                this->value = other.value;
            }
            
            friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
                os << foo.value;
                return os;
            }
    };
    
    template<typename E, typename F>
    auto map(std::vector<E>& input, F f) -> std::vector<decltype(f(input[0]))>
    {
        std::vector<decltype(f(input[0]))> result;
        for (auto& element : input) {
            result.emplace_back(f(element));
        }
        return result;
    }
    
    int main()
    {
        auto test = std::vector<Foo>{};
        test.emplace_back(3);
        test.emplace_back(2);
        test.emplace_back(1);
        auto test2 = map(test, [](auto& i) { std::cout << i << std::endl; return std::move(i); });
        return test2.size();
    }
    

    Not a single copy constructor will be called if you compile this with a C++17 compiler. And the only ugly part is the fact that you have to write decltype twice.

    I have a few questions about this solution:

    Oh, crap. Is now the right time to admit my C++ skills are a bit rusty? I currently don't use it at my job.

    • Why is input passed by non-const reference?

    It feels wrong to even try and use const here, since my lambda destroys the entries in the original vector. I only did that because you wanted to see a solution that doesn't copy at all. I have no idea whether it would compile, but moving elements out of a constant vector sounds like either a compile-time error or UB to me.

    AFAIK it makes it impossible to bind to temporaries, so wherever I use this map, I have to make sure I have an lvalue there by assigning my intermediate results to local variables (and if I forget it, it all compiles fine but creates memory access violation). Am I missing something?

    I don't think that's true. You have to make sure any object you pass by reference is not used past its lifetime, whether the reference is const or not.

    • Why is i passed by non-const reference? Will it work with const reference?

    See above.

    • Is it important for the lambda to return rvalue reference? What if it returned a value?

    Technically, std::move creates an xvalue, but you don't have to worry about that. The important point here is that you have to tell the compiler that the object pointed to by the reference i is no longer needed and that it can therefore be moved instead of copied. That's what std::move does and basically all you need to know.

    if it returned rvalue reference when it is expected to return a value?

    As I said, my C++ is a bit rusty, but I think the inferred return type is actually a value type, not an rvalue reference. Just forget the term "rvalue reference" unless you want to dig deep into the standard; the more useful and intuitive term is "temporary" (=xvalue if I'm not mistaken).

    Are there any downsides to making each and every function in the entire program return rvalue reference instead of value?

    In C++17, you can just return local objects created by the function by value and rely on copy elision. If what you're returning is not a local object, then you have to be careful - only move if you don't need the original object anymore.

    • Shouldn't you have f(std::forward(element)) instead of f(element)? Would it change anything?

    No, it wouldn't change anything, since forward doesn't turn references into rvalue references.

    • If I made a function std::vector<float> f(std::vector<char>& c) { return map(map(c, charToInt), intToFloat); }, would it still be non-copy? If not, what changes do I have to do?

    Why are you even worried about copies if you're using primitive types?

    That said, my implementation does not magically eliminate copies if your lambda doesn't move the value out of the original vector. Off the top of my head, I wouldn't even know how to do that.

    • What would change if map() was made non-template that accepts std::function?

    I don't know what you're proposing here. If you want it to work of arbitrary vectors, map needs to be a template either way unless I'm missing something here.

    • How would the code above behave in C++14? What changes would you make then?

    Guaranteed copy elision is a C++17 feature, so the return value of map may be copied depending on the compiler and optimization level.

    I wish I could answer those questions on my own. But I tried and tried, and the exact meaning of move semantics still escapes me.

    I'd love to help you, but I don't know how. One thing you should definitely remember is that you should always make your move constructors noexcept, since the STL containers won't use them otherwise (and because it's also a good idea).

    Disclaimer: I answered all of the above to the best of my knowledge. Some answers might be incorrect. As I said, I currently don't use C++ on a daily basis.

    @dfdub said in I have no multiple inheritance and I must scream:

    (The flaws in your snippet were also quite obvious. It seems like you deliberately tried to create as many copies as possible.)

    I wrote them in the most natural way, like I would write them in any other language

    With "obvious" I meant the fact that you didn't use references in the loop and the lambda.



  • @gąska said in I have no multiple inheritance and I must scream:

    Or you could load DLL and find appropriate function. You can even generate those functions on the fly with JIT compiler!

    This is reflection :facepalm:



  • @dfdub
    Update: I just tried it out. The result of making both the vector and the lambda function argument const is, counterintuitively, not a compile error. Apparently, const rvalue references are a thing. If I don't change Foo, the copy constructor will be used when I call emplace_back on the result vector. If I change the signature of the move constructor to take a constant rvalue reference, the move constructor is called again.

    However, none of this seems useful in real-world code, since this completely defeats the point of having a move constructor, so you shouldn't do it anyway.



  • @dfdub said in I have no multiple inheritance and I must scream:

    Apparently, const rvalue references are a thing.

    That's... pretty much exactly how passing string literals to functions that take strings work.



  • @ben_lubar said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    Apparently, const rvalue references are a thing.

    That's... pretty much exactly how passing string literals to functions that take strings work.

    Not really. It may be an example of a temporary that is constant, but you don't need any other type than const char* for that.

    Deliberately creating a const T&& doesn't make sense because the only reason rvalue references exist in the first place is to allow overloads that perform better and destroy the original object. Which you can't if it cannot be modified.



  • @dfdub said in I have no multiple inheritance and I must scream:

    @ben_lubar said in I have no multiple inheritance and I must scream:

    @dfdub said in I have no multiple inheritance and I must scream:

    Apparently, const rvalue references are a thing.

    That's... pretty much exactly how passing string literals to functions that take strings work.

    Not really. It may be an example of a temporary that is constant, but you don't need any other type than const char* for that.

    Deliberately creating a const T&& doesn't make sense because the only reason rvalue references exist in the first place is to allow overloads that perform better and destroy the original object. Which you can't if it cannot be modified.

    const std::string & is what I've always used in C++. Once it's const, adding more & doesn't change how able you are to use that type.


  • Banned

    @dfdub said in I have no multiple inheritance and I must scream:

    And in the one case they did think it through - rvalue references and move semantics - the ruleset they came up with is so convoluted - by necessity! - that you cannot write code that takes full advantage of move semantics without descending into madness, and taking your code with you so no other programmer can decipher it.

    That statement is so obviously wrong to anyone who ever actually worked with C++1x that I don't even know what to say.

    template<typename A, typename B, typename F>
    std::vector<B> map(std::vector<A> v, F f)
    {
    std::vector<B> r;
    for (auto i: v) r.emplace_back(f(i));
    return r;
    }
    
    std::vector<Foo> v;
    // fill in some instances of Foo
    map(v, [](auto i) { std::cout << i << std::endl; return i; });
    

    How to rewrite the code above to avoid any copies being made? Would anything change if map() was made non-template?

    I just had some time, so here's my solution:

    #include <iostream>
    #include <vector>
    
    struct Foo
    {
        private:
            int value;
            
        public:
            Foo(int value) {
                this->value = value;
            }
            
            Foo(const Foo& other) {
                std::cout << "Copy constructor" << std::endl;
                this->value = other.value;
            }
            
            Foo(Foo&& other) noexcept {
                std::cout << "Move constructor" << std::endl;
                this->value = other.value;
            }
            
            friend std::ostream& operator<<(std::ostream& os, const Foo& foo) {
                os << foo.value;
                return os;
            }
    };
    
    template<typename E, typename F>
    auto map(std::vector<E>& input, F f) -> std::vector<decltype(f(input[0]))>
    {
        std::vector<decltype(f(input[0]))> result;
        for (auto& element : input) {
            result.emplace_back(f(element));
        }
        return result;
    }
    
    int main()
    {
        auto test = std::vector<Foo>{};
        test.emplace_back(3);
        test.emplace_back(2);
        test.emplace_back(1);
        auto test2 = map(test, [](auto& i) { std::cout << i << std::endl; return std::move(i); });
        return test2.size();
    }
    

    Not a single copy constructor will be called if you compile this with a C++17 compiler. And the only ugly part is the fact that you have to write decltype twice.

    I have a few questions about this solution:

    Oh, crap. Is now the right time to admit my C++ skills are a bit rusty? I currently don't use it at my job.

    Better late than never, I guess.

    • Why is input passed by non-const reference?

    It felt wrong to even try and use const here, since my lambda destroys the entries in the original vector. I only did that because you wanted to see a solution that doesn't copy at all. I have no idea whether it would compile, but moving elements out of a constant vector sounds like either a compile-time error or UB to me.

    I guess I should have made my question more specific. Why non-const lvalue reference, and not rvalue reference or passing by value? All three make sense when mutating the vector, and the latter two don't require lvalue. They also intuitively seem better choice for when you're not just destroying (or as we functional folks say, consuming) the elements, but also the original vector itself. But I know that C++ is anything but intuitive, and I guessed there was some reason you picked non-const lvalue reference over the other two (e.g. because it's impossible to make it work with them).

    AFAIK it makes it impossible to bind to temporaries, so wherever I use this map, I have to make sure I have an lvalue there by assigning my intermediate results to local variables (and if I forget it, it all compiles fine but creates memory access violation). Am I missing something?

    I don't think that's true. You have to make sure any object you pass by reference is not used past its lifetime, whether the reference is const or not.

    I specifically remember there being a rule in C++03 that allows you to pass temporary values (rvalues) to a function taking const reference, but not to one taking non-const reference. I doubt they removed it in future revisions. But they might have extended it to work in more cases.

    • Is it important for the lambda to return rvalue reference? What if it returned a value?

    Technically, std::move creates an xvalue, but you don't have to worry about that. The important point here is that you have to tell the compiler that the object pointed to by the reference i is no longer needed and that it can therefore be moved instead of copied. That's what std::move does and basically all you need to know.

    AFAIK, on the technical level, std::move only converts an expression to xvalue reference, and for it to have any effect, the xvalue-ness of the argument has to be preserved throughout entire call stack up and down, with some 56 additional rules on how to make it work as expected. I've never found a comprehensive, precise and consistent manual on what those rules are. And believe me that I tried.

    if it returned rvalue reference when it is expected to return a value?

    As I said, my C++ is a bit rusty, but I think the inferred return type is actually a value type, not an rvalue reference. Just forget the term "rvalue reference" unless you want to dig deep into the standard; the more useful and intuitive term is "temporary" (=xvalue if I'm not mistaken).

    It's not that useful in the context of figuring out when copies do and don't happen. From the caller's point of view, everything the function returns is a temporary, but only some of these temporaries make copy elision possible. Or is move semantics only about the inside of the function and not its interface?

    Are there any downsides to making each and every function in the entire program return rvalue reference instead of value?

    In C++17, you can just return local objects created by the function by value and rely on copy elision. If what you're returning is not a local object, then you have to be careful - only move if you don't need the original object anymore.

    Way to avoid answering the question. Moving out of non-local variables aside, what's the worst that can happen if I always use return std::move(x) instead of return x?

    • Shouldn't you have f(std::forward(element)) instead of f(element)? Would it change anything?

    No, it wouldn't change anything, since forward doesn't turn references into rvalue references.

    So what is it for?

    • If I made a function , would it still be non-copy? If not, what changes do I have to do?

    Why are you even worried about copies if you're using primitive types?

    Okay, how about std::vector<std::map<std::string, std::function<void()>> f(std::vector<std::shared_ptr<std::mutex>>& m) { return map(map(m, mutexesToOpenglTextures), openglTexturesToMaps); }? It's completely nonsensical, but let's assume for a moment that it isn't. All types involved are large and non-trivial to copy. Will you now answer my question of whether chaining your map() function works?

    That said, my implementation does not magically eliminate copies if your lambda doesn't move the value out of the original vector. Off the top of my head, I wouldn't even know how to do that.

    I see. So it's not applicable to arbitrary functions. So it probably doesn't work in chains either.

    • What would change if map() was made non-template that accepts std::function?

    I don't know what you're proposing here. If you want it to work of arbitrary vectors, map needs to be a template either way unless I'm missing something here.

    Let's say I only want a map that works only on one specific pair of vector types. Just an arbitrary nonsensical example I ask about because I'm trying to understand move semantics and I heard it works differently in template vs. non-template functions in some cases.

    • How would the code above behave in C++14? What changes would you make then?

    Guaranteed copy elision is a C++17 feature, so the return value of map may be copied depending on the compiler and optimization level.

    Any way to avoid that? Will wrapping it in std::move() work?

    I wish I could answer those questions on my own. But I tried and tried, and the exact meaning of move semantics still escapes me.

    I'd love to help you, but I don't know how. One thing you should definitely remember is that you should always make your move constructors noexcept, since the STL containers won't use them otherwise (and because it's also a good idea).

    Wow, really? Shouldn't it automatically resolve the noexcept-ness of outer function with noexcept(expr) syntax? Are default move constructors/assignments noexcept?

    Disclaimer: I answered all of the above to the best of my knowledge. Some answers might be incorrect. As I said, I currently don't use C++ on a daily basis.

    It's almost as if you're proving my point that C++ move semantics are too complicated for a normal human to comprehend. Don't get me wrong - I appreciate your effort in helping me understand.

    @dfdub said in I have no multiple inheritance and I must scream:

    (The flaws in your snippet were also quite obvious. It seems like you deliberately tried to create as many copies as possible.)

    I wrote them in the most natural way, like I would write them in any other language

    With "obvious" I meant the fact that you didn't use references in the loop and the lambda.

    Because I don't know which types of references are appropriate in which cases, and I know that some references in some places make copy elision impossible.


  • Trolleybus Mechanic

    1. Put both into the class, but add an IsAuthenticated flag
    class BlakeyClass
    {
       public bool IsAuthenticated = false;
       public date DateStuff;
    
       void DoRequestThang()
       {
          if(IsAuthenticated)
          {
               AuthenticationStuff();
          }
    
          DoDateStuff(); ...
       }
       
    
    }
    
    1. Create a Utility class. Implement an interface by calling that class
    class BlakeyClassUtil
    {
       static void DateStuff(Date d)
       {
          // do stuff here
       }
       static bool AuthenticationStuff()
       {
          // do stuff here
       }
    }
    
    interface IBlakeyDate
    {
       void DateStuff(Date d);
    }
    
    interface IBlakeyAuthentication
    {
       bool AuthenticationStuff();
    }
    
    class BlakeyRequestAuth : IBlakeyAuthentication
    {
          void MakeRequest()
          {
                if(this.AuthenticationStuff()) // do stuff
          }
    
          bool AuthenticationStuff : IBlakeyAuthentication.AuthenticationStuff
          {
                return BlakeyUtil.AuthenticationStuff();
          }
    }
    
    

    For #2, implement those interfaces as many times as you want, but all they do is call the static class's shared logic.



  • @lorne-kates What!!! An actual reply to the question after 546 posts of C++ standards wankery!!!!!


  • Trolleybus Mechanic

    @blakeyrat said in I have no multiple inheritance and I must scream:

    @lorne-kates What!!! An actual reply to the question after 546 posts of C++ standards wankery!!!!!

    Tobe Faire, I didn't read the entire thread.

    It works out pretty well sometimes.


  • Trolleybus Mechanic

    Also, forgot to mention, you can now have a controller of sorts.

    Make your base BlakeyRequest class. Inherit from it as much as you want, slapping interfaces as you see fit. You could have a BlakeyHTTPRequest and a BlakeyGopherRequest.

    The controller takes care of what features each class has.

    class BlakeyRequestController
    {
        void DoStuff(BlakeyRequestBase)
        {
           if(BlakeyRequestBase is IBlakeyAuthentication)
          {
              if(! ((IBlakeyAuthentication)BlakeyRequest).DoAuthentication() )
              {
                throw new exception("authenticate your shit, you dumb motherfucker");
              }
          }
    
          ... more stuff ..
        }
    }
    


    • Why is input passed by non-const reference?

    It felt wrong to even try and use const here, since my lambda destroys the entries in the original vector. I only did that because you wanted to see a solution that doesn't copy at all. I have no idea whether it would compile, but moving elements out of a constant vector sounds like either a compile-time error or UB to me.

    I guess I should have made my question more specific. Why non-const lvalue reference, and not rvalue reference or passing by value?

    This way, you can choose whether you want to destroy the original vector or not - it depends on the lambda you pass in.

    All three make sense when mutating the vector, and the latter two don't require lvalue.

    All of them work with temporaries, see below.

    They also intuitively seem better choice for when you're not just destroying (or as we functional folks say, consuming) the elements, but also the original vector itself.

    If your map function is always supposed to destroy the original vector, then you might as well declare the parameter an rvalue reference to force people to explicitly use std::move. I didn't because my implementation doesn't necessarily do that.

    AFAIK it makes it impossible to bind to temporaries, so wherever I use this map, I have to make sure I have an lvalue there by assigning my intermediate results to local variables (and if I forget it, it all compiles fine but creates memory access violation). Am I missing something?

    I don't think that's true. You have to make sure any object you pass by reference is not used past its lifetime, whether the reference is const or not.

    I specifically remember there being a rule in C++03 that allows you to pass temporary values (rvalues) to a function taking const reference, but not to one taking non-const reference. I doubt they removed it in future revisions. But they might have extended it to work in more cases.

    A quick Google search brought up the following quote from the standard:

    A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.

    Your nested map() calls should be fine, even if the parameter isn't const. Everything else would have surprised me, TBH.

    The only thing that's problematic AFAIK is returning a reference to a local variable from a function.

    See: https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary

    • Is it important for the lambda to return rvalue reference? What if it returned a value?

    Technically, std::move creates an xvalue, but you don't have to worry about that. The important point here is that you have to tell the compiler that the object pointed to by the reference i is no longer needed and that it can therefore be moved instead of copied. That's what std::move does and basically all you need to know.

    AFAIK, on the technical level, std::move only converts an expression to xvalue reference, and for it to have any effect, the xvalue-ness of the argument has to be preserved throughout entire call stack up and down, with some 56 additional rules on how to make it work as expected.

    The rules are actually quite easy: As soon as you have a named reference, it's not a temporary anymore and overloads taking rvalue references will not be called unless you use std::move.

    Examples:

    • The return value of a function is a temporary. When passing it to another function without assigning it to a variable, overloads taking rvalue references will be preferred.
    • Inside a function that takes an rvalue reference as a parameter, that parameter is no longer a temporary. You have to explicitly std::move it to tell the compiler to treat it like a temporary.

    It's really quite simple. As I said, think about temporaries and ignore the term "rvalue reference" if you want to understand the mechanism. std::move is a way of telling the compiler "I know this is not a temporary, but please pretend that it is; I promise not to use this variable anymore".

    I've never found a comprehensive, precise and consistent manual on what those rules are. And believe me that I tried.

    If you read the rules in the standard, you'll only confuse yourself. Just remember the simple rule above.

    if it returned rvalue reference when it is expected to return a value?

    As I said, my C++ is a bit rusty, but I think the inferred return type is actually a value type, not an rvalue reference. Just forget the term "rvalue reference" unless you want to dig deep into the standard; the more useful and intuitive term is "temporary" (=xvalue if I'm not mistaken).

    It's not that useful in the context of figuring out when copies do and don't happen. From the caller's point of view, everything the function returns is a temporary, but only some of these temporaries make copy elision possible. Or is move semantics only about the inside of the function and not its interface?

    The important part of the interface is the parameter list. If a function has an overload that takes an rvalue reference (or only takes rvalue references), that means you can pass it a temporary (either artificially created by std::move or an actual temporary) and it will most likely make this variable unusable, but avoid copies.

    Edit: You can also use std::move() on the caller's side if the function takes a parameter by value; this will also avoid a copy.

    I don't see a reason why you'd ever want to explicitly return an rvalue reference (i.e. declare T&& func();). Don't do that; just declare a function that returns by value and if the caller doesn't assign it to a variable, it's still a temporary.

    Are there any downsides to making each and every function in the entire program return rvalue reference instead of value?

    In C++17, you can just return local objects created by the function by value and rely on copy elision. If what you're returning is not a local object, then you have to be careful - only move if you don't need the original object anymore.

    Way to avoid answering the question. Moving out of non-local variables aside, what's the worst that can happen if I always use return std::move(x) instead of return x?

    In C++17, there is literally no difference if x is a local variable. So you should use the latter since it's more readable.

    If x is not a local variable, but a reference to another object, then it makes the difference between allowing the caller to destroy the original object and not allowing the caller to destroy the original object.

    • Shouldn't you have f(std::forward(element)) instead of f(element)? Would it change anything?

    No, it wouldn't change anything, since forward doesn't turn references into rvalue references.

    So what is it for?

    std::forward is meant for template code that wants to avoid copies, but not turn lvalue references into rvalue references. The difference to std::move is that it'll leave lvalue references alone. The caller can then either pass a temporary or a regular reference and the template will magically do the right thing.

    • If I made a function , would it still be non-copy? If not, what changes do I have to do?

    Why are you even worried about copies if you're using primitive types?

    Okay, how about std::vector<std::map<std::string, std::function<void()>> f(std::vector<std::shared_ptr<std::mutex>>& m) { return map(map(m, mutexesToOpenglTextures), openglTexturesToMaps); }? It's completely nonsensical, but let's assume for a moment that it isn't. All types involved are large and non-trivial to copy. Will you now answer my question of whether chaining your map() function works?

    See above. Yes, it does. If you use my implementation, you'll need to explicitly std::move the elements out of the vector inside your closures to avoid copies.

    That said, my implementation does not magically eliminate copies if your lambda doesn't move the value out of the original vector. Off the top of my head, I wouldn't even know how to do that.

    I see. So it's not applicable to arbitrary functions. So it probably doesn't work in chains either.

    It does, see above.

    • What would change if map() was made non-template that accepts std::function?

    I don't know what you're proposing here. If you want it to work of arbitrary vectors, map needs to be a template either way unless I'm missing something here.

    Let's say I only want a map that works only on one specific pair of vector types. Just an arbitrary nonsensical example I ask about because I'm trying to understand move semantics and I heard it works differently in template vs. non-template functions in some cases.

    I still don't know what you're getting at (I'm a little tired), but I hope I already answered it above.

    • How would the code above behave in C++14? What changes would you make then?

    Guaranteed copy elision is a C++17 feature, so the return value of map may be copied depending on the compiler and optimization level.

    Any way to avoid that? Will wrapping it in std::move() work?

    That will certainly avoid the copy constructor. There may still be an unnecessary move constructor call.

    I wish I could answer those questions on my own. But I tried and tried, and the exact meaning of move semantics still escapes me.

    I'd love to help you, but I don't know how. One thing you should definitely remember is that you should always make your move constructors noexcept, since the STL containers won't use them otherwise (and because it's also a good idea).

    Wow, really? Shouldn't it automatically resolve the noexcept-ness of outer function with noexcept(expr) syntax?

    They theoretically could, no idea why they don't. Probably either because it's unintuitive or because of backwards compatibility.

    Are default move constructors/assignments noexcept?

    Defaulted move constructors magically do the right thing, i.e. they are noexcept if all other move constructors they have to call are noexcept. See: https://stackoverflow.com/questions/18653726/is-the-default-move-constructor-defined-as-noexcept

    Disclaimer: I answered all of the above to the best of my knowledge. Some answers might be incorrect. As I said, I currently don't use C++ on a daily basis.

    It's almost as if you're proving my point that C++ move semantics are too complicated for a normal human to comprehend. Don't get me wrong - I appreciate your effort in helping me understand.

    I hope I changed your mind. :)

    @dfdub said in I have no multiple inheritance and I must scream:

    (The flaws in your snippet were also quite obvious. It seems like you deliberately tried to create as many copies as possible.)

    I wrote them in the most natural way, like I would write them in any other language

    With "obvious" I meant the fact that you didn't use references in the loop and the lambda.

    Because I don't know which types of references are appropriate in which cases, and I know that some references in some places make copy elision impossible.

    If you're looping over a container, always use references unless you have a specific reason to copy the value and use const references where appropriate. When you're passing a (large) object to a function, always pass by (const) reference unless you have a specific reason not to (allowing null, passing ownership via std::unique_ptr).

    @blakeyrat said in I have no multiple inheritance and I must scream:

    What!!! An actual reply to the question after 546 posts of C++ standards wankery!!!!!

    I already flagged one of my own posts and asked the mods to move our whole conversation. I could start a new thread now, but then the conversation would be split between two threads and people would probably still reply here anyway.


Log in to reply