The return-value-and-out-parameter idiom


  • Winner of the 2016 Presidential Election

    I'm occasionally working on an old C++ codebase. It's a library that processes certain data structures; let's just say it processes library records.

    Depending on how you compile the program, the library records have an ID that's 32 or 64 bits long. Implementing that is easy: You just typedef a type in one header file and use that type consistently across the entire codebase:

    #ifdef USE_32BIT
    typedef uint32_t record_id_t;
    #else
    typedef uint64_t record_id_t;
    #endif
    

    That's not what the original author did. (Well, that typedef exists, but it's not used anywhere.) The second best approach (already a lot worse) to the problem would be using #ifdef everywhere the type is used:

    #ifdef USE_32BIT
    uint32_t get_record_id() {
      //[ get unsigned 32 bit integer ]
    }
    #else
    uint64_t get_record_id() {
      //[ get unsigned 64 bit integer ]
    }
    #endif
    
    // etc.
    

    That is also not what the original author of the library did. Instead, he went with something like this:

    #ifdef USE_32BIT
    uint32_t get_library_id() {
      //[ get unsigned 32 bit integer ]
    }
    #endif
    uint64_t get_library_id() {
      //[ get unsigned 64 bit integer ]
    }
    
    // etc.
    

    Which doesn't compile, so he decided to be smart and invented the return-value-and-out-parameter idiom:

    #ifdef USE_32BIT
    uint32_t get_library_id(uint32_t* out) {
      //[ get unsigned 32 bit integer ]
      *out = result;
      return *out;
    }
    #endif
    uint64_t get_library_id(uint64_t* out) {
      //[ get unsigned 64 bit integer ]
      *out = result;
      return *out;
    }
    
    // etc.
    

    Yep, every single function in the entire codebase that returns a record ID is duplicated for both possible return types and expects an out parameter to which it assigns the return value as well.

    :headdesk:



  • In Go, you aren't even limited to one return value!

    func CrappyCrap(out1, out2 *uint32) (uint32, uint32) {
        //[ get unsigned 32 bit integer ]
        *out1 = result1
        *out2 = result2
        return *out1, *out2
    }
    

    Filed under: so it wouldn't help, there's no overloading


  • area_pol

    @asdf said:

    he decided to be smart and invented the return-value-and-out-parameter idiom

    Is he Korean by any chance?



  • Do any of those wonderful and strange languages you know of and hopefully not use for serious do the opposite and make every function one input, one output? As long as union/struct construction wasn't also a function this wouldn't be impossible, if pointless.



  • Easy rule how not to suck at programming: Every time you copy/paste a piece of code, stop and think "Is there any way I can avoid this?". All other patterns and best practices come out of this.


  • BINNED

    Incidentally, the saddest moments is when the answer is either "no" or "yes, but it would imply some major refactoring, oh fuck, the deadline".

    At which point you do it, apologize in the comments and take up drinking...



  • In Java, you can actually have two functions which differ only in return type. Not at compile time though - you have to use a maven plugin to rename one of the functions. Sounds completely ridiculous, right? Well, actually Bukkit did this for backward compatibility when they had to change the return type of some functions. Code that was compiled before the change binds to the version of the function with the expected return type, and code that is compiled after the change binds to the other function. Somehow they got the best of both worlds thanks to Java selecting functions based on return type.


  • Discourse touched me in a no-no place

    @LB_ said:

    In Java, you can actually have two functions which differ only in return type.

    No, you can only do that in JVM. The Java language prohibits you from writing code which does this. So you can only do it if you have a non-Java mechanism for generating the class definitions that do it. There are compilers other than the Java one that can target the JVM… 😄


  • Discourse touched me in a no-no place

    @asdf said:

    Yep, every single function in the entire codebase that returns a record ID is duplicated for both possible return types and expects an out parameter to which it assigns the return value as well.

    The out parameter stuff is how C compilers tend to implement returning structures that are larger than a machine word or two (though they don't support overloading by type).


  • Garbage Person

    @dkf said:

    There are compilers other than the Java one that can target the JVM… 😄
    Yeah, but how many of those are actually used?

    If you ignore the official languages,

    Reads like "List of experimental doctoral thesis and basement wankery"

    Compare to

    Reads like "List of experimental doctoral thesis and basement wankery with a few implementations that might arguably be useful"



  • Well, BIT has no functions...


  • I survived the hour long Uno hand

    @Weng said:

    how many of those are actually used?

    Clojure, Groovy, and Scala are fairly widespread. Groovy is being held up as a serious choice for enterprise web development, we ran into a lot of recommendations to switch to Groovy when we were doing our analysis (we picked Node instead).

    ColdFusion is also widely known, that's what we're switching off of. As far as I know it's on the downslide into oblivion though.

    @Weng said:

    with a few implementations

    So you mean, those official ones you threw out for JVM? 🚎

    Seriously ,though, remove C#, VB.NET, and Powershell, and you're looking pretty slim.



  • @Yamikuronue said:

    Scala

    ITYM Extended Cool

    This manual describes the programming language Cool: the Classroom Object-Oriented Language. Cool is a small language that can be implemented in a one semester course. Still, Cool retains many of the features of modern programming languages including objects, static typing, and automatic memory management. Cool 2015 is a new language differing from most previous versions of Cool. Indeed Cool 2015 is a subset of Scala; in a ridiculous act of hubris, we will call Scala “Extended Cool.”



  • It has been a long time since I used "advanced" C++, but I recall using an explicit template instantiation to a similar problem ages ago.



  • Only Google considers C++ templates as "advanced" ;) though really, templates are overkill for solving this problem.


  • BINNED

    One has Jython and Scala the other only has IronPython, JVM wins.


  • BINNED

    You can count how many times the word template appears in this page, and this is the list of bug fixes in a 2012 compiler. Advanced or not, something that trips people as smart as compiler writers, better be used only when absolutely necessary. Even better would be to ditch c++ and start using Rust.


  • Garbage Person

    @Yamikuronue said:

    Clojure, Groovy, and Scala are fairly widespread. Groovy is being held up as a serious choice for enterprise web development, we ran into a lot of recommendations to switch to Groovy when we were doing our analysis (we picked Node instead).

    The only people I hear raving about Clojure and Scala are the ones who were all over Ruby on Rails a few years ago.

    And as someone who heads a team that does enterprise Web Development, I am highly skeptical of Groovy.

    1. Ain't no enterprise gonna use anything with that kind of name. Corporate dignity requires that we all work in Fortran, COBOL, C, Visual Basic, C# and Java because their names are the least in-jokiest and most approachable by dunderheaded business lizards.
    2. Is that Enterprise as in "rapid development, high maintainability, low resource cost and Shiny Shiny for the ADHD management set" or Enterprise as in "great for making yet another crap internal off the books LOB site that will become business critical and be universally hated by everyone who has to maintain it" or Enterprise as in "highly paid consultants required to build it, highly paid consultants required to maintain it and highly paid consultants required to use it"

    Because that word means all of those things.



  • Templates have horrible syntax and mechanics, but they're not advanced - advanced implies you would use it in rare circumstances. I'd love to use Rust, but it has some major issues that prevent me from using it.


  • Java Dev

    What I've read on rust looks pretty interesting, but with a 200kloc codebase at work I'm unlikely to get to use it.



  • I suppose this means you can't use the Gradle build environment for Java and have to use Maven or the like instead?

    After all, Gradle is written in Groovy.


  • Garbage Person

    Our javaheads are maven users.


  • I survived the hour long Uno hand

    @Weng said:

    I am highly skeptical of Groovy

    Me too, for all the same reasons I dislike Ruby 😄

    We're not rapid development by any stretch, so much more the second definition.


  • Winner of the 2016 Presidential Election

    @Onyx said:

    Incidentally, the saddest moments is when the answer is either "no" or "yes, but it would imply some major refactoring, oh fuck, the deadline".

    When I saw that crap, I immediately tried to refactor the code. Problem: The code for 32 and 64 bit started to diverge over time. There are a lot of wonderful, subtle (and completely unnecessary) differences which client code may now actually depend on. Also, there are no tests whatsoever for the whole library, so I just added a third, sane method without the out parameter wherever I encountered this pattern. Whenever the two functions for 32 and 64 bit had subtle differences, I added a sizeof check inside the new method.

    @Onyx said:

    At which point you do it, apologize in the comments and take up drinking...

    My head still hurts because of all the 🍻 I had to drink yesterday.

    @NeighborhoodButcher said:

    Is he Korean by any chance?

    Nope, Polish. Paging @Maciejasjmj. ;)

    @dse said:

    You can count how many times the word template appears in this page, and this is the list of bug fixes in a 2012 compiler. Advanced or not, something that trips people as smart as compiler writers, better be used only when absolutely necessary.

    Templates are hard to handle for compilers. For programmers, not so much, unless you have to write complicated templates yourself instead of just using existing ones. And even template metaprogramming is not rocket science, it's just a very weird way of writing scripts for the compiler.

    @Weng said:

    I am highly skeptical of Groovy

    It's nice for writing build scripts (Gradle) and/or scripts that interact with your Java application, e.g. complicated tests. I wouldn't use it for large applications, though.



  • @asdf said:

    And even template metaprogramming is not rocket science, it's just a very weird way of writing scripts for the compiler.

    But, but, macros are evil, therefore templates must be great!


  • BINNED

    Nothing that weird is a good idea. Next time when you see a compile-time sizeof hack of a dummy argument (I would call it out just to be related to this topic) determines what code should be compiled in, look away because macros are at least honest. C++ is for weirdos.

    🚎


  • Discourse touched me in a no-no place

    @Weng said:

    Yeah, but how many of those are actually used?

    You'll need to define a filtering metric to answer that, but Scala seems pretty popular among people doing stuff like automated trading of shares. I cannot vouch for whether this is a good idea, and I've no idea why it happened. ;)

    Different domains seem to prefer different languages. Experience with popularity within one is not a good indicator for popularity in others.



  • Macros don't adhere to scope, type system, or other language constructs - they're just dumb text substitution. Templates do adhere to scope, type system, and other language constructs.



  • @LB_ said:

    Macros in C++ don't adhere to scope, type system, or other language constructs - they're just dumb text substitution. Templates do adhere to scope, type system, and other language constructs.

    Which neatly outlines what's great about macros. Of course, macros and templates can work together in perfect harmony:

    template< typename K, typename V, typename C = MapKeyCompare<K> >
    struct Map: std::map<K, V, C> { };
    
    #define MAP_KEY_COMPARE_DEFAULT(TYPE_, COMPARE_) \
        template<>struct MapKeyCompare<TYPE_>: COMPARE_ {}; \
    
    /** Key comparison for Maps */
    template<typename T> struct MapKeyCompare: std::less<T> {};
    
    /** String comparison functor for Maps */
    struct StringCompare {
        bool operator()(const char* s1, const char* s2) const {
            return strcmp(s1, s2) < 0;
        }
    };
    
    MAP_KEY_COMPARE_DEFAULT(char*, StringCompare);
    MAP_KEY_COMPARE_DEFAULT(const char*, StringCompare);
    

    Don't make me post my RTTI implementation. It will scare you.


  • area_pol

    @asdf said:

    Nope, Polish.

    I apologize for my countrymen.


  • Winner of the 2016 Presidential Election

    @tar said:

    But, but, macros are evil

    Yes, they are. Every time I see macros in a C++ codebase that do more than define a constant, I know I'll find some bug related to them.

    @NeighborhoodButcher said:

    I apologize for my countrymen.

    I didn't know you were from Poland. Funny coincidence: The best programmer in our team is Korean, so my company is the exact opposite of yours. ;)


  • Banned

    @cartman82 said:

    Easy rule how not to suck at programming: Every time you copy/paste a piece of code, stop and think "Is there any way I can avoid this?". All other patterns and best practices come out of this.

    I wanted to like this post but I misclicked and viewed raw instead. Sorry.



  • @Gaska said:

    I wanted to like this post but I misclicked and viewed raw instead. Sorry.

    ............. that's ok?



  • @asdf said:

    Every time I see macros in a C++ codebase that do more than define a constant, I know I'll find some bug related to them.

    So you write a unit test that isolates the issue and then fix it?



  • @asdf said:

    Nope, Polish. Paging @Maciejasjmj

    Wasn't me. Ask @Gaska, I think he's the C++ guy here.

    @NeighborhoodButcher said:

    I apologize for my countrymen.

    Just how many of us are here?


  • Banned

    Neither was me. My code might be crazy sometimes, but it's because I try to write shortest code possible - quite the opposite of what OP is about.


  • Discourse touched me in a no-no place

    @Gaska said:

    I try to write shortest code possible

    Try writing and maintaining some Perl. That'll break that habit and persuade you that a little verbosity is actually a good thing. 😃



  • @Maciejasjmj said:

    I think he's the C++ guy here.

    I think me and @asdf are the C++ guys around here. At least you can be sure C++ is my favorite language and my language of choice when starting new projects. I won't defend its horrible syntax though.


  • Winner of the 2016 Presidential Election

    @LB_ said:

    I think me and @asdf are the C++ guys around here.

    Actually, I use 4 different programming languages on a daily basis, two of which are Java and C++. 6 if you count quick 'n' dirty Python and Bash scripts. So I probably don't count as a "C++ guy". ;)


  • Banned

    @LB_ said:

    At least you can be sure C++ is my favorite language and my language of choice when starting new projects.

    :doing_it_wrong:


  • Banned

    @asdf said:

    So I probably don't count as a "C++ guy".

    For me, everyone who knows about the most vexing parse is C++ guy.


  • Discourse touched me in a no-no place

    @LB_ said:

    my language of choice when starting new projects. I won't defend its horrible syntax though.

    Oh dear.



  • @Gaska said:

    For me, everyone who knows about the most vexing parse is C++ guy.

    Bu earlier you said you weren't? How do you know of a good metric for determining if someone is a C++ guy if you yourself are not?



  • I plead guilty to actually doing the same thing in C#... once.
    My requirement here was "I want to be able to initialize the thing on the same line I declare it, and I don't want to repeat the type".

        public static T[] MakeArrayOfOne&lt;T&gt;(out T[] arr) where T:new()
        {
            arr = new T[] { new T() };
            return arr;
        }
    
        SomeType[] myArray = MakeArrayOfOne(out myArray);
        SomeOtherType[] array2 = MakeArrayOfOne(out array2);
    

    Edit: Uhm, how do I code sample? This doesn't seem to work like SO...
    You use triple backquotes in front + after the code, on a separate line -a
    Edit2: OK, thanks.
    You're welcome!


  • Banned

    @LB_ said:

    Bu earlier you said you weren't?

    I said I weren't the author of code in OP.


  • ♿ (Parody)

    @asdf said:

    Depending on how you compile the program, the library records have an ID that's 32 or 64 bits long.

    This sort of thing makes the hair on the back of my neck stand up. What if a 32-bit compile needs to deal with data from a 64-bit version? Of course, this is so anonymized that we don't know if such a thing will exist, and I'm probably reading too much into the terms library and record.

    If they're just local handles (which the principle of most WTF says they aren't) then it's no problem.


  • kills Dumbledore

    @LB_ said:

    How do you know of a good metric for determining if someone is a C++ guy if you yourself are not?

    I can determine a good mechanic without having to know how to strip down an engine myself



  • @Medinoc said:

    My requirement here was "I want to be able to initialize the thing on the same line I declare it, and I don't want to repeat the type".

    Yikes... Why couldn't you just do:

    var myArray = MakeArrayOfOne<SomeType>();
    

    Or even simpler:

    var myArray = new[] { new SomeType() };
    

    without a static method.


  • Winner of the 2016 Presidential Election

    @boomzilla said:

    What if a 32-bit compile needs to deal with data from a 64-bit version?

    Fortunately, this is not a use case that we have to support.

    @boomzilla said:

    I'm probably reading too much into the terms library and record.

    ^^^ this



  • @Maciejasjmj said:

    Yikes... Why couldn't you just do:

    var myArray = MakeArrayOfOne<SomeType>();
    

    Or even simpler:

    var myArray = new[] { new SomeType() };
    

    without a static method.

    Because I didn't think of the first way (I don't have the reflex of using var, especially when working on legacy code) and I genuinely didn't know it was possible to use new[] without a type in it. Had I begun the work tomorrow, I'd have probably used the latter method.


Log in to reply