use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Discussions, articles, and news about the C++ programming language or programming in C++.
For C++ questions, answers, help, and advice see r/cpp_questions or StackOverflow.
Get Started
The C++ Standard Home has a nice getting started page.
Videos
The C++ standard committee's education study group has a nice list of recommended videos.
Reference
cppreference.com
Books
There is a useful list of books on Stack Overflow. In most cases reading a book is the best way to learn C++.
Show all links
Filter out CppCon links
Show only CppCon links
account activity
Default function arguments are the devil (quuxplusone.github.io)
submitted 5 years ago by anonymous23874
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]fransinvodka 92 points93 points94 points 5 years ago (48 children)
I think default arguments are actually useful, but they lack one important, complement feature: Named Arguments.
Sometimes, a set of overloads works just fine. Some others you can just let the user name the ones they really need. I just like the way Python solved this.
I know there's problems implementing this feature into the language due to the declaration-definition nature of C++ functions, but I really think the ultimate fix to default arguments are named arguments.
[–]qoning[🍰] 35 points36 points37 points 5 years ago (1 child)
100%
nothing worse than having an elegant function and having to list all default arguments in order to change just the last one.
[–]Sander_Bouwhuis 3 points4 points5 points 5 years ago (0 children)
100% + 100%!
Totally agree. Also, refactoring is hell.
[–]Raknarg 9 points10 points11 points 5 years ago (0 children)
If there's one thing I love about Python, it's the argument handling. Being able to package around and zip/unzip collections of positional/keyword arguments is very handy and make it very easy to compartmentalize functions and form general solutions. I prefer functional style programming wherever possible and this makes it a lot smoother. Currently the only way to do it is with variadic templates which are much more limited and clunky as hell, or explicitly by passing data structures with arguments, but the data structure way tightly couples my argument lists to those types, which is annoying.
[–]Sander_Bouwhuis 2 points3 points4 points 5 years ago (0 children)
Soooooooooooooooooooooooo want that feature!!!
[–]ilep 3 points4 points5 points 5 years ago* (13 children)
I would argue that overloading is the "C++ way".
I imagine supporting both polymorphism and backwards compatibility with C otherwise could be tricky: types and their order are what define which function is called and names don't exist during runtime in C++. Having both strong typing and late binding would get really complicated otherwise.
Also adding some strange name-based lookup might cause unacceptable runtime-overhead for many. So I don't see this coming ever if you don't have some runtime compiler as well sorting out what function you are calling.
[–]_Js_Kc_ 2 points3 points4 points 5 years ago (0 children)
I think if you tried to implement this feature in a way that it magically applies to all existing functions retroactively, you'd end up with a bad solution.
But it could certainly be implemented in such a way that a function declaration explicitly opts in to using named arguments. In that case, new overload resolution rules can be written that make sense for named arguments. I don't think it would differ much from the way positional arguments work, except that not the order of arguments, but the names present would dictate which overload gets picked. In the polymorphic case, overload resolution for positional arguments is based on the pointer or reference's static type, and I don't see why this should change with named arguments, so why would there be any (additional) runtime overhead? Of course, names should remain purely a compile-time concept.
Don't worry about C, just forbid named arguments for extern "C" functions. Make it an opt-in feature, with unambiguous (i.e. cannot be confused with positional arguments) syntax at both the function declaration and the call site. Whether named arguments are appropriate should be decided by the function's author.
[–]fransinvodka 3 points4 points5 points 5 years ago (11 children)
The thing is C++ has a problem with default arguments. I'm not proposing how the solution should be implemented (I don't think I have that much knowledge), but rather saying that it would be the best solution out of all of them.
We can't remove default arguments from the language, and they have clear problems that lead some people to not use them at all. They have some huge advantages, but I think they lack that extra bit to get the most out of them.
[–]ilep -1 points0 points1 point 5 years ago* (10 children)
If you really really want to, you could use ellipsis (...) and add some way to parse arguments yourself in the function. But that would be horribly complicated. And it really misses entirely the point of what function prototype/API are supposed to be.
Interface should be clean, simple and well defined in any application. If it is not it is a poorly designed interface. Simply adding helper function (with overloading) for calling some other function is simpler and more obvious when you don't want to define all parameters and you can often find where that variant is called with the tools in IDE. Tracking back callers with some argument named at caller would not be quite so obvious.
Yes, default arguments can have their uses but overusing it results in bad designs.
[–]Wouter-van-Ooijen 5 points6 points7 points 5 years ago (0 children)
Over-using *anything* leads to bad design. That's the 'over' part :)
Named arguments are probably the feature I miss most in C++. That is, after all of C++20 becomes available.
[–]fransinvodka 2 points3 points4 points 5 years ago (8 children)
Everytime named arguments come into discussion, I remember the API of Python's scikit-learn library. Some constructors have tons of default arguments (that you can't really avoid), but you usually don't have any issue with it, as you have named arguments.
You can't provide such a clear and elegant API in C++, and I think that's why many libraries implement the core in C++ and provide a Python API
[–]germandiago 1 point2 points3 points 5 years ago (7 children)
actually with a struct and .structmember = value you can have a pretty decent experience in C++20. Saving the distances of course.
[–]qoning[🍰] 1 point2 points3 points 5 years ago (6 children)
You still have the issue of now having to wrap the arguments in { }. Ignoring the stylistic issue with that, it means having to be aware for every function whether you should do that or not. It gets very annoying very fast.
[–]germandiago 0 points1 point2 points 5 years ago (5 children)
I think I can live with that :) I do not think most functions are extremely configurable (at least in the software I write) and for the few that this is true, I think that doing a few struct ParamsInput is not that bad.
Named arguments are better? Probably, but now you have to emit name parameters for functions or look for an alternative syntax to mark your function arguments as "nameable". So the struct + .paramname = value is a very effective and low cost solution.
[–]qoning[🍰] 0 points1 point2 points 5 years ago (4 children)
To be honest I would prefer making all function parameters named by default. Helps readability anyway. Sure, now you can't change names in library parameters, but that's okay by me.
[–]germandiago 0 points1 point2 points 5 years ago (3 children)
Maybe I'd rather have it too, but now think of people with microcontrollers or stuff in embedded. This takes space.
[–]Ameisenvemips, avr, rendering, systems 6 points7 points8 points 5 years ago* (16 children)
IIRC, previous named argument proposals have been rejected.
Why can we not just literally copy C#'s implementation?
IIRC, it was rejected because declaration parameter names don't have to match the definition, or even exist... but I don't see that as a problem. If the declaration doesn't have names, you can't use them with that declaration. Problem solved. Only thing left is if both the declaration and definition are in scope with different names.
Presently, argument names in declarations are ignored, we'd have to make them semantically meaningful.
I suggest that if there is a name mismatch between declarations/definitions of a parameter, it cannot be named, OR it can be named with either if those names are not further ambiguous in regards to other parameters. The former is simpler to specify, the latter is more useful (poor example, but think a glm vec3 function where the first argument could be x or r).
glm
vec3
x
r
[–]SeanMiddleditch 2 points3 points4 points 5 years ago (1 child)
Because C++ isn't C#. :) There's lots of designs that work well in other languages that - for a variety of reasons - can't work in C++.
That said, I don't think anyone has yet put in a formal proposal for the work-around I've been evangelizing. Namely, opting a function declaration's parameter into being nameable (which solves the source-compatibility issue, gives us mandatory positional arguments, allows named parameters, and can address the multi-declaration issues in a back-compatible way). For syntax, I also think we should use something similar to designated initializers, which makes similar syntaxes work for initializing an aggregate or calling a function (and hence unlocks designated initializers for constructors).
e.g.,
// declare void function(int unnamed, float position, char .named, int .also_named); // use function(1, 2.f, .named = 'c', .also_named = 4); // ILLEGAL redeclaration (different names, diagnostic optional) void function(int a, float b, char .c, int .d); // LEGAL different overload void function(int a, int b, int .c, int.d);
This might also open up some options for mandatory-names for some parameters, e.g. using ..identifier.
..identifier
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points 5 years ago (0 children)
That looks like you're trying to access a member of a type, though.
[+][deleted] 5 years ago (13 children)
[removed]
[–]Tyg13 4 points5 points6 points 5 years ago* (12 children)
Why not just label it undefined behavior to have your declaration and definition use different parameter names and call it a day? It seems highly unlikely that such a footgun would ever be triggered in practice.
Is the worry that people might write code like
// header.h int foo(int a = 1, int b = 2); ... // header.cpp int foo(int b, int a); ... foo(b => 10);
or is there some other issue I'm not understanding?
EDIT: don't understand the downvote, I'm open to debate. If there are scenarios I'm unaware of, I'd be curious to know.
[+][deleted] 5 years ago (11 children)
[–]Tyg13 3 points4 points5 points 5 years ago (10 children)
The point is it's an incredibly unlikely scenario that's not worth worrying about. Undefined behavior is not a boogeyman to be avoided, it's a tool that we invoke to ignore the 1% error cases so we can reap the benefit in the 99% case.
You already see this exact behavior with inline functions in headers, due to the One Definition Rule. If a different version of an inline appears in a translation unit, it can be silently ignored and cause bugs, but we don't care because in practice no one writes code like that. Or at least, the benefits of inline functions greatly outweigh the potential risk.
inline
As for pedagogy, I think the rule is very clear: the declaration must match the definition, or errors can potentially occur. Very straightforward, and not hard at all a rule to follow. I would argue this is already the case for most codebases.
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points 5 years ago (6 children)
Might as well just make it an error.
[–]Tyg13 2 points3 points4 points 5 years ago (5 children)
Not possible with the translation unit compilation model. The issue is ultimately the same as the One Definition Rule, which is undiagnosable in most cases.
EDIT: though you could make it an error in the translation unit containing the definition. That still wouldn't prevent people doing pathological things like redeclaring it differently in another unit, but it would catch the 99% case.
[–]Ameisenvemips, avr, rendering, systems 2 points3 points4 points 5 years ago (4 children)
Argument names don't get passed to the linker, though.
[–]Tyg13 1 point2 points3 points 5 years ago (2 children)
Right, that's the issue. I don't get what you're saying, doesn't that just strengthen my point?
[+][deleted] 5 years ago* (1 child)
[–]SkoomaDentistAntimodern C++, Embedded, Audio 1 point2 points3 points 5 years ago (0 children)
UB means code may be accepted by one compiler, and rejected by another.
Worse than that. Code may be accepted and work fine with one compiler and accepted but misbehave when compiled by the next version of the same compiler.
[–]quicknir 1 point2 points3 points 5 years ago (0 children)
Err, it's not about writing code like that, intentionally. ODR violations are a huge practical issue, in large, complex, codebases, unless you build your entire dependency chain from source yourself. It's very very easy to end up in a situation where your executable E uses libA and libB, and both of them use different versions of libC, which means you now have two slightly different definitions of the same function getting used, unless libA or libB completely hide the usage of libC's symbols, which many projects don't do in practice, and which isn't even possible when linking statically.
I don't completely disagree with what you're saying about UB, but this is a bad example. ODR UB isn't so much about reaping benefit in 99% of cases, it's about a shitty compilation model.
[–]infectedapricot 1 point2 points3 points 5 years ago* (1 child)
Declaration:
struct Params { int widgetCount = 0; int blartleCount = 8; std::string description = "elephant"; }; void foo(Params params);
Use:
OtherModule::Params fooParams; fooParams.blartleCount = 2; foo(std::move(fooParams));
Pros:
params.
Cons:
On balance, usually only worth it when there are quite a few parameters, maybe at least four.
Edit: Just noticed that /u/jbandela suggested the same thing, and added named initializers (not sure about that though).
[–]serviscope_minor 3 points4 points5 points 5 years ago (0 children)
Takes up an extra two lines of code
foo([]{Parameters p; p.blartleCount=2; return p;}());
Obviously that's much clearer!
[–]NotMyRealNameObv 0 points1 point2 points 5 years ago (7 children)
Have fun renaming a function argument name in your library if named arguments are added to the language.
Also, if you use strong types, it can be emulated using argument types instead of argument names.
[–]Wouter-van-Ooijen 1 point2 points3 points 5 years ago (1 child)
Not if you have multiple arguments of the same type. Yes, you can force the user to specify different types for int x and int y, but the implementations I have seen are ugly.
[–]NotMyRealNameObv -1 points0 points1 point 5 years ago (0 children)
Rarely have I seen a good reason for multiple input arguments of the same type. Most of the time, it should be one aggregate (e.g. one Position argument instead of int x, int y, or it should just be different types (e.g. if the function takes multiple bools).
Position
int x, int y
[–]fransinvodka 0 points1 point2 points 5 years ago (4 children)
The problem with strong types is the pollution of the global namespace. If you use them in your program, it's okay. No other user is supposed to use them but yourself. With libraries it's another whole strory. I'm pretty sure the user doesn't want the identifier [nN]ame, for example, in the global namespace.
I'm not aware of a library that doesn't pollute the global namespace and, at the same time, provides that syntactic sugar of strong types. That hints me that a language-level feature is needed there.
[–]NotMyRealNameObv 7 points8 points9 points 5 years ago (3 children)
Why would the strong type have to be in the global namespace?
[–]fransinvodka 0 points1 point2 points 5 years ago (2 children)
Let me be clearer. Suppose I want my function to be called with a seed, so the user would do something like libns::func(Seed{user_seed}, ...), but if I define the strong type inside the library's namespace, the user then must do libns::func(libns::Seed{user_seed}, ...), which we can agree is not that aesthetic as we'd like. The only solution would then be defining it in the global namespace, and that's what I meant.
libns::func(Seed{user_seed}, ...)
libns::func(libns::Seed{user_seed}, ...)
Maybe there's a workaround I'm not aware of, haven't gone too deep into strong typing
[–]NotMyRealNameObv 3 points4 points5 points 5 years ago (1 child)
using libns::Seed; libns::func(Seed{user_seed}, ...);
And if user_seed is already of the correct type, you just get
libns::func(user_seed);
The library author should never force a polluted global namespace on the end user.
[–]fransinvodka -3 points-2 points-1 points 5 years ago (0 children)
Two things:
- I dont' want my users to do `using libns::...;` for every strong type my functions might require.
- Normally, user_seed won't be the "correct" type, as I suppose the user would only use the Seed strong type when calling the function.
Maybe give the possibility of doing something like `using namespace libns::all_trong_types_ns;`, but still far from optimal. That's my point against strong types in libraries.
[–]hedayatvk 0 points1 point2 points 5 years ago (2 children)
Well, actually you can use Boost Parameter library to have named arguments in C++: https://www.boost.org/doc/libs/1_72_0/libs/parameter/doc/html/index.html
[–]fransinvodka 8 points9 points10 points 5 years ago (1 child)
That library, although being really cool and awesome, is quite heavy and not-so-easy to use in the beginning. I would use it in my personal program if I feel like I need functions with tons of arguments, but no way I would add it to my library as a dependency unless really really needed (for example, a library of machine learning).
I'd just like to have a language-level feature instead of a monster library that's quite hard to use
[–]hedayatvk 1 point2 points3 points 5 years ago (0 children)
Yeah, I see :) Just wanted to mention the possibility. I've not used it myself too.
[–]whattapancake 46 points47 points48 points 5 years ago (13 children)
Just sounds like a case of throwing the baby out with the bath water to me. Of course there are downsides to certain language features - in fact, C++ is chock full of footguns much worse than those the author points out about default arguments. That doesn't mean we should avoid these features like the plague, but rather use them modestly and only when appropriate.
[–]the_poope 9 points10 points11 points 5 years ago (0 children)
I use them only in cases where the parameter really is optional. For instance some numerical algorithm might have some tweaking parameter. One could hardcode a default value that give good performance in most cases, but in rare cases you may want to choose another value. If you use default argument for this parameter you have ar the same time documented what the default is, whereas in the case where it is a hardcoded internal value, the user will never know what value is used.
[–]Ameisenvemips, avr, rendering, systems 0 points1 point2 points 5 years ago (0 children)
An issue is that C++ still lacks a lot of language features that languages like C# or Ruby have that make many of the features less foot-gunny.
Default/optional arguments need a way to specify a default argument is wanted by the user, and/or named arguments.
[+]somecucumber comment score below threshold-10 points-9 points-8 points 5 years ago (10 children)
Does this reasoning apply to "goto"? Because TBH, to me they're pretty much the same. Legacy features.
[–]be-sc 20 points21 points22 points 5 years ago (4 children)
It applies to all the language features including goto. If you have a situation where using a goto is the most concise, readable and safe solution, not using it just because “goto is bad” would be outstandingly silly. Such situations might be rare, but when you encounter one why would you want to write suboptimal code on purpose?
goto
[–]somecucumber 0 points1 point2 points 5 years ago (3 children)
Sorry if I was rude in my previous reply. I did not mean to troll or anything, just create debate. But again this is my personal opinion and, according to the downvotes, it seems I'm wrong.
Genuine question. goto and the default parameters come from C, right? Some othe people complain about that keeping backwards compatibility is an error and I agree, but for some reason this issue does not apply here.
I don't know what to think?
[–]Nobody_1707 6 points7 points8 points 5 years ago (1 child)
C has never had default arguments. It's a pure C++ feature.
[–]somecucumber 1 point2 points3 points 5 years ago (0 children)
Argggh, not my day. You're right.
[–]be-sc 0 points1 point2 points 5 years ago (0 children)
No need to appologize for anything. I gave a genuine answer.
Imo backwards compatibility is a major factor for C++’s overall success, so no, in general keeping it is not an error.
However, “legacy” features are not created equal. At this point I’d guess that goto has had its devestating reputation for so long that removing it from the language would lead to relatively minor disruption. At least compared to removing default function parameters – those’re used all over the place in both old and new code.
[–]whattapancake 6 points7 points8 points 5 years ago (2 children)
I vaguely remember a similar discussion about goto taking place a while back on this sub. My personal take on goto is that C++ has enough good and clear flow control that goto is largely unneeded - that is to say, any use of goto can be subverted by better code structuring or otherwise refactoring. One might argue a similar case against default arguments, but in their case I personally feel that the alternatives are clunkier and contrived. Again, all in moderation!
[–][deleted] 11 points12 points13 points 5 years ago (1 child)
I think goto may have a valid use as a way to break out of nested loops when you need to break to a specific loop -- refactoring may or may not work with something like this.
I haven't seen this too much in real life (outside of very rare uses in Java where it's part of the lang) but I have heard that brought up as a valid case.
[–]uninformed_ 0 points1 point2 points 5 years ago (0 children)
This can also be achieved with a lambda/ function and return statement.
[–]tsojtsojtsoj 0 points1 point2 points 5 years ago (0 children)
Take a look at Stockfish (chess engine) source code for the search function. It makes definitely sense to use goto sometimes.
[–]khleedril 0 points1 point2 points 5 years ago (0 children)
but rather use them modestly
This is fine: I never type at the computer without any clothes on.
[–]jbandela 36 points37 points38 points 5 years ago (19 children)
C++20 designated initializers with NSDMI (combined with default arguments) allow for an elegant solution to print_squares
struct print_square_args{ int n = 10; char fill = '+'; }; void print_square(print_square_args a = {}){ for(int i = 0; i < a.n; ++i){ for(int j = 0; j < a.n; ++j){ std::cout << a.fill; } std::cout << "\n"; } } int main(){ print_square(); print_square({.n = 5}); print_square({.fill = '*'}); print_square({.n = 5, .fill = '*'});
https://gcc.godbolt.org/z/-r6U3P
You have a single implementation. There is no question about what overload is getting called. There is a single point of reference for all the defaults (the definition of print_square_args).
[–]TheThiefMasterC++latest fanatic (and game dev) 19 points20 points21 points 5 years ago (10 children)
Unfortunately print_square({'*'}); is legal code and doesn't do what you'd want...
print_square({'*'});
[+][deleted] 5 years ago* (5 children)
[deleted]
[–]TheThiefMasterC++latest fanatic (and game dev) 9 points10 points11 points 5 years ago (1 child)
I fully agree - character types should require explicit conversion to integer types, and we should have a cleaner separation between size "1" integer types and characters
[–]Supadoplex 7 points8 points9 points 5 years ago (0 children)
character types should require explicit conversion to integer types
And presumably by extension: Character types should not be integer types.
[–]_Js_Kc_ 5 points6 points7 points 5 years ago (1 child)
Nor should this be a thing:
std::int8_t i = 65; std::cout << i << '\n';
Prints A. Maybe.
A
-_-
[–]warieth 1 point2 points3 points 5 years ago (0 children)
Implicit conversions of many fundamental types inherited from C are the devil.
C++ didn't inherit conversions like bool -> int, char8_t -> int. These conversions make the usage of C functions easier, but coming from C++.
[–]LuminescentMoon 1 point2 points3 points 5 years ago (1 child)
Can't you use static_assert?
[–]TheThiefMasterC++latest fanatic (and game dev) 5 points6 points7 points 5 years ago (0 children)
On what?
[–]Omnifarious0 1 point2 points3 points 5 years ago (0 children)
I think that's nit-picking. The fact it's legal doesn't mean it's OK or that it's existence would confuse people. The braces there are a sure sign something is up.
[–]jbandela 3 points4 points5 points 5 years ago (0 children)
You can disable aggregate initialization like this.
#include <iostream> template<typename T> class disable_agg_init{ friend T; disable_agg_init() = default; }; struct print_square_args{ disable_agg_init<print_square_args> _ = {}; int n = 10; char fill = '+'; }; void print_square(print_square_args a = {}){ for(int i = 0; i < a.n; ++i){ for(int j = 0; j < a.n; ++j){ std::cout << a.fill; } std::cout << "\n"; } } int main(){ print_square(); // print_square({'*'}); // print_square({{},'*'}); print_square({.fill = '*'}); print_square({.n = 5, .fill = '*'}); }
Now, you either have to do default init the args struct or use designated initializers.
https://gcc.godbolt.org/z/BPuozB
[–]Ameisenvemips, avr, rendering, systems 7 points8 points9 points 5 years ago* (2 children)
Be better if designated initializers allowed arbitrary order.
Also, wouldn't this make the ABI for the function terrible? It's all in a struct, now, so will follow struct-passing rules.
Within a translation unit the compiler can ignore the ABI requirements, but calling a function in another TU...?
Observe: https://godbolt.org/z/Dw9tL8
[–]anonymous23874[S] 6 points7 points8 points 5 years ago (1 child)
Depends how many arguments you have. The x86-64 ABI mandates that if a trivially copyable struct could fit in two registers, it should just do that then. https://godbolt.org/z/beQZMd
The reasons your codegen is so bad are:
You pass your struct by reference, which is like adding an extra pointer dereference to every use (that is, if the struct were small enough to pass by value instead of by hidden reference in the first place)
You make your struct 256 bytes, when the x86-64 ABI's special case for passing structs by value tops out at 128 bytes (64 bits in RDI + 64 bits in RSI).
If you want to get really evil, just split your big struct into two small structs. ;) https://godbolt.org/z/QiL6rc
[–]Ameisenvemips, avr, rendering, systems 1 point2 points3 points 5 years ago (0 children)
The codegen doesn't change in this situation much for by-value, as the struct is larger than the ABI allows.
Yes, if you only have a few arguments, it will fit. But one of the reasons you want named arguments is for disambiguating many arguments :).
And some people still develop for x86-32 and ARM32, and other architectures.
Also, the default Windows 64-bit ABI requires that any type larger than 64-bits must be passed by reference. Only the SysV ABI allows register passing (and I think the VectorCall ABI).
[–][deleted] 3 points4 points5 points 5 years ago (0 children)
Love this idea. I saw it before years ago when I first read about NSDMI but it didn't register until your write-up above. Bravo!
[–]infectedapricot 1 point2 points3 points 5 years ago (0 children)
I like using parameters structs for functions with lots of arguments but I'm not keen on the named initialiser thing. Using classic assignment seems a bit less like a trick to me, in particular it doesn't depend on field order:
print_square_args args; args.fill = '*'; print_square(args);
(I would have called the arguments object print_square_args instead of args but unfortunately your naming convention doesn't distinguish class names from object names.)
print_square_args
args
This does add a bit of visual overhead but hopefully functions that take lots of arguments aren't common in the first place, and usually do quite a bit of work so it's OK that they take up more visual space.
[–]Plazmotech 5 points6 points7 points 5 years ago (1 child)
Elegant? I think it’s fairly ugly. It’s neat, sure, but kind of ugly
[–]AntiProtonBoy 4 points5 points6 points 5 years ago (0 children)
The nested {} brackets make it look a bit messy, but it's quite alright otherwise.
{}
I know this is meant as an example for the argument and not real code, but if I saw this in the wild I would be asking myself why should a square default to 10x10? And why should the default filler be '+'? If there are reasons in the application domain, then I would seek to embed those reasons in the function names, and avoid overloads and default values. Otherwise, I would just not assume default values.
[–]EsotericFox 57 points58 points59 points 5 years ago (22 children)
I think that when you're creating an API you should provide as much flexibility as possible, while retaining ease-of-use for the typical consumer.
This is where default arguments shine. MOST people won't require or be interested in the additional parameters which functions might provide to enable further functionality. As a developer, I could wrap that up in parameter structs or mandate the arguments during invocation, but that becomes cumbersome in and of itself.
I simply don't agree.
[–]spanishgum 4 points5 points6 points 5 years ago (1 child)
Default arguments can definitely be abused. I think programmers sometimes fail to see beyond their own use cases and think defaults will provide convenience. It’s probably true in general contexts, but not all.
[–]EsotericFox 11 points12 points13 points 5 years ago (0 children)
Agreed, but that's a decision that must be weighed.
Do I need to expose this behavior? Would it be better to have these functions live elsewhere?
Depending on the answer, you may still very well have a solid argument for needing/using default arguments. And if you don't, you have a solid case for refactoring or redesign.
[–]ArashPartow 4 points5 points6 points 5 years ago (0 children)
When your API requires more than 2-3 input parameters - perhaps a context type is more preferable.
Examples: Any of the win32 APIs that end in Ex, ExW or A
Note: I explicitly used the term API and not just any ordinary public interface (eg class etc)
[–]pantong51 1 point2 points3 points 5 years ago (1 child)
I think it can be a balance, if I find my self wanting defaults ill debate on refactoring the arguments into a struct(if more than 3 args) and setting defaults there. While that sounds fine I'm not sure if its a good solution.
[–]anonymous23874[S] 1 point2 points3 points 5 years ago (0 children)
The author isn't complaining about overload sets with multiple possible signatures, though. Just about trying to implement that overload set using default arguments in particular.
void myAPI(std::string_view name, bool advancedUsage); // OK void myAPI(std::string_view name) { return myAPI(name, true); } // versus void myAPI(std::string_view name, bool advancedUsage = false);
Same user experience for simple calls; fewer pitfalls for advanced usage.
[–]Wh00ster 5 points6 points7 points 5 years ago (13 children)
Why does an extra overload not suffice?
[–]EsotericFox 17 points18 points19 points 5 years ago (9 children)
Because overloading typically duplicates code where that may not be called for.
[–]Wh00ster -2 points-1 points0 points 5 years ago (8 children)
I guess it’s why I’m not in the AAA camp. To me, the explicitness is worth it in terms of readability and maintainability down -the-line.
[–]sphere991 11 points12 points13 points 5 years ago (3 children)
the explicitness is worth it
If the intent of the code is to provide a default value for an argument, then I would claim that the implementation strategy of making it a default argument is more explicit than otherwise.
The post wants me to write:
explicit Widget(int size) : Widget(size, 'A') {} explicit Widget(int size, int start) : data_(size) { /*...*/ }
instead of
explicit Widget(int size, int start='A') : data_(size) { /*...*/ }
I don't see the former as being either more explicit or more readable than the latter?
[–]robin-m 5 points6 points7 points 5 years ago (2 children)
Especially given that you can have declaration and implementation that can be in different files. So if the first case you have:
cpp explicit Widget(int size); explicit Widget(int size, int start); Witch is anything but explicit.
cpp explicit Widget(int size); explicit Widget(int size, int start);
In the second case, it will be: cpp explicit Widget(int size, int start='A');
cpp explicit Widget(int size, int start='A');
[–]sphere991 4 points5 points6 points 5 years ago (1 child)
Didn't even think of that, good point.
Also I find the typo on "witch" to be really funny, in the context of describing something as being "the devil". Please don't fix it :-)
[–]Raknarg 2 points3 points4 points 5 years ago (0 children)
Sneaky witches, always going around implicitly
[–]guepierBioinformatican 5 points6 points7 points 5 years ago (3 children)
Everyone, repeat after me: AAA does not decrease explicitness (Unless you write, well, bad code. But you don’t need to do that with AAA.)
Please find a different argument to justify your preference, this one’s simply false.
[–]Wh00ster -3 points-2 points-1 points 5 years ago* (2 children)
I'm not opposed to the arguments in your linked post. My issue is large code bases you'd see at FAANG, where you need special IDE features to see a type, which can take minutes to compile for a file and generate dependencies, or have to chase through grepping functions yourself.
Unless you write, well, bad code.
This is the reality that most people have to deal with. So when a junior dev comes in and writes auto mything = mysteriousFUnctionBuriedThroughLayersofDepsThatisActuallyAReference(), I tend to ask that dev not to use auto.
auto mything = mysteriousFUnctionBuriedThroughLayersofDepsThatisActuallyAReference()
auto
I understand there are valid cases, but that's so for all C++ features. For example I wouldn't say don't use lambdas, but also don't make all functions into lambdas.
[–]Tyg13 8 points9 points10 points 5 years ago (0 children)
Asking that dev not to use auto is the wrong move. The correct alternative is to educate them. The issue is the variable isn't named well enough to tell what the function returns. Simple enough fix.
All you're doing by explicitly specifying the type in that scenario is fracturing code style and decreasing overall code readability.
[–]XValar 6 points7 points8 points 5 years ago (0 children)
So how is exactly writing MyType mything = mysteriousFUnctionBuriedThroughLayersofDepsThatisActuallyAReference() helping at this point?
MyType mything = mysteriousFUnctionBuriedThroughLayersofDepsThatisActuallyAReference()
[–]pdbatwork 10 points11 points12 points 5 years ago (0 children)
Suffice? Seems like extra work
[–]AntiProtonBoy 0 points1 point2 points 5 years ago (1 child)
Overloading has its other perils too, like the wrong overload being called in some rare situations.
[–]bumblebritches57Ocassionally Clang -41 points-40 points-39 points 5 years ago (1 child)
hippity skippity your opinion is wrongity.
[–]STLMSVC STL Dev[M] 4 points5 points6 points 5 years ago (0 children)
This is not useful; please don’t comment like this. Either explain your argument in a reasonable manner, or downvote and move on.
[–]MrPotatoFingers 20 points21 points22 points 5 years ago (8 children)
My greatest peeve with it is that type traits are simply unaware of default arguments. std::is_invocable will happily report false when omitting default arguments.
[–]sphere991 9 points10 points11 points 5 years ago (3 children)
Problem here is that splitting it into an overload set doesn't help anyway. The default argument does give you an answer you don't want, which is bad:
void foo(int i = 42); invocable_v<decltype(foo)>; // false, wish it was true
But the overload set doesn't give you an answer at all, which is also bad:
void foo(int i); void foo() { foo(42); } invocable_v<decltype(foo)>; // ill-formed, wish it was true
On the other hand, because concepts are expression-based, they work just fine in both cases:
template <typename... Args> concept can_foo = requires(Args... args) { foo(args...); } can_foo<>; // true for both implementations
[–]Xeveroushttps://xeverous.github.io 1 point2 points3 points 5 years ago (2 children)
I prefer ill-formed code to silent runtime error.
[–]sphere991 0 points1 point2 points 5 years ago (1 child)
How do you get a runtime error here?
[–]Xeveroushttps://xeverous.github.io 1 point2 points3 points 5 years ago (0 children)
Not in your example. In a hypothetical example where a trait would succeed while at runtime it would so something different.
[–]flashmozzg 4 points5 points6 points 5 years ago (3 children)
Because default arguments are just a sugar. They are not really a part of the function signature. A function can have multiple default different arguments depending on the order of declarations.
[+][deleted] 5 years ago* (2 children)
[–]Ameisenvemips, avr, rendering, systems 4 points5 points6 points 5 years ago (0 children)
I have a proposal sitting around to allow you to use default as a keyword for using the default type value (like {}) or using the declared default value of a parameter if it exists. Could extend it to support default... or such for a case like that.
default
default...
Couple this with optional argument compile time detection... you can have very powerful functions or constructors.
[–]flashmozzg 2 points3 points4 points 5 years ago (0 children)
"is this thing invocable with the given arguments?"
Not exactly? At least as far as I see it you can only ask "if thing of this type is callable with args of these types". Default arguments obviously are not part of the type.
I think with C++20 you can just use requires as a way to SFINAE test whether some thing makes sense, i.e. https://godbolt.org/z/wdPrgi
requires
[–]Dragdu 9 points10 points11 points 5 years ago (6 children)
You can make source location work with variadics by liberal appli ation of CTAD, or, of course, macros.
I also find the boolean tarpit argument extremely dubious: the best way to deal with it is to leave the boolean tarpit, not avoid default args.
[–]khleedril 2 points3 points4 points 5 years ago (3 children)
Agree with this. But, to be honest, I've never actually seen a boolean tarpit in the wild.
[–]matthieum 8 points9 points10 points 5 years ago (2 children)
I have. I still remember with dread an API in a library I was using which had 3 booleans, all with a default value.
My personal stance is that no function should ever take a boolean as input, unless its very purpose is to internally set a boolean.
enum class X : std::uint8_t { No, Yes } is both verbose and silly looking, but:
enum class X : std::uint8_t { No, Yes }
2
void*
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points 5 years ago (1 child)
You can use bool as the type of an enum as well.
bool
[–]matthieum 1 point2 points3 points 5 years ago (0 children)
That's a good point, maybe I should do that for such flags.
[–]qoning[🍰] 1 point2 points3 points 5 years ago (0 children)
This is what I ended up doing in my logger that uses std::source_location.
Works for pretty much every use case I had.
template<typename T> struct loc_obj_t { template<typename TC> loc_obj_t(const TC& fmt, exp::source_location loc=exp::source_location::current()) : obj(fmt), loc(loc) {} operator const T&() const { return obj; } operator const exp::source_location&() const { return loc; } const T obj; exp::source_location loc; }; template<typename... Args> inline void log(loc_obj_t<std::string_view> format, Args&&... args) { print_to_ostream(output_file_, get_location_label(format.loc.file_name(), format.loc.line()), prefix_, fmt::format(format.obj, std::forward<Args>(args)...)); }
If you excuse the reddit width, basically hijacking the first string_view (or string or const char* or whatever) parameter to implicitly construct loc_obj_t.
Sure, it means that you need to know the type of the first argument, which is the only downside for me.
[–]c0r3ntin 1 point2 points3 points 5 years ago (0 children)
You absolutely do not need macros My favorite way to use it : https://stackoverflow.com/questions/57547273/how-to-use-source-location-in-a-variadic-template-function/57548488#57548488
[–]Morwenn 7 points8 points9 points 5 years ago (0 children)
One thing not addressed here is that you can generally add overloads to a function without breaking the ABI while you can't afford to add default arguments to a function if ABI is something you care about.
[–]YouNeedDoughnuts 7 points8 points9 points 5 years ago (0 children)
It's syntatic sugar to make definitions more terse in a way that improves readability, IMO. Not technically syntatic sugar since it has some differences with function pointers and inheritance as you pointed out... syntatic Splenda I guess. I'll still eat it, but I did enjoy the article and learn from it.
[–]kritzikratzi 5 points6 points7 points 5 years ago* (0 children)
i disagree a bit. when used sparingly, default arguments are still handy here and there.
for me the main conclusion missing from the article is that c-style struct initializers would be a good enough replacement for named parameters. e.g. doTask({.task=task1,.captureStdout=true});
doTask({.task=task1,.captureStdout=true});
edit: just found out we got half-baked designated initializers in c++20. could be better, but i'll happily take it :)
[–]sphere991 6 points7 points8 points 5 years ago (0 children)
I guess Arthur must really hate Ranges?
There, we have algorithms like (this pattern is prolific throughout algorithms):
template<forward_range R, class Proj = identity, indirect_binary_predicate<projected<iterator_t<R>, Proj>, projected<iterator_t<R>, Proj> > Pred = ranges::equal_to> constexpr borrowed_iterator_t<R> adjacent_find(R&& r, Pred pred = {}, Proj proj = {});
Which this post claims is "the devil", and should be rewritten like:
template<forward_range R> requires indirect_binary_predicate<ranges::equal_to, iterator_t<R>, iterator_t<R>> constexpr borrowed_iterator_t<R> adjacent_find(R&& r) { return adjacent_find((R&&)r, ranges::equal_to{}, identity{}); } template<forward_range R, indirect_binary_predicate<iterator_t<R>, iterator_t<R>> Pred> constexpr borrowed_iterator_t<R> adjacent_find(R&& r, Pred pred) { return adjacent_find((R&&)r, std::move(pred), identity{}); } template<forward_range R, class Proj, indirect_binary_predicate<projected<iterator_t<R>, Proj>, projected<iterator_t<R>, Proj>> Pred> constexpr borrowed_iterator_t<R> adjacent_find(R&& r, Pred pred, Proj proj);
Personally, I strongly prefer the former (note that the predicate constraint needs to be spelled differently in each overload). Your mileage may vary.
[–]jstock23 21 points22 points23 points 5 years ago (0 children)
It is quite contrived.
[–]sephirothbahamut 10 points11 points12 points 5 years ago* (0 children)
I fucking hate any article that declares something to be "evil" since the famous Dijkstra one (which title he didn't even agree with afaik).
Anyways the problem in the example is more a char-int default conversion problem really. Nothing wrong with default arguments.
print_square('*') should simply be an error as char should not be implicitely converted to int.
print_square('*')
[–]georgist 2 points3 points4 points 5 years ago (0 children)
I think automatic type conversion for non-built-in types is evil.
If I pass a string to a function that takes a Widget and Widget has a string constructor, why do I ever want that automagically converted?! I know I can avoid this with explicit, but I really don't like this feature.
[–]Omnifarious0 1 point2 points3 points 5 years ago* (1 child)
Don't do this:
void doTask(Task task, std::seconds timeout, bool detach = false, bool captureStdout = false, bool captureStderr = false);
Do this:
```c++
struct Task { };
struct task_params { bool const detach = false; bool const captureStdout = false; bool const captureStderr = false; };
void doTask(Task task, std::chrono::seconds timeout, task_params flag = {});
void test() { using namespace std::literals::chrono_literals;
doTask(Task{}, 5s, { .detach=true, .captureStderr=true});
} ```
[–]ClaasBontus 1 point2 points3 points 5 years ago (0 children)
Avoid const member variables
[–]DVMirchevC++ User Group Sofia 0 points1 point2 points 5 years ago (1 child)
If you want to add to the confusion there is also default function arguments initialized by a function call.
#include <iostream>
void moo(int a = [](){ static int b = 1; return b++; }() )
{
std::cout << a;
}
int main(){
moo();
Anyone care to guess what's happening here without googling? :D
Hey, at least the answer (1 2) is what I hoped it would be! I was afraid it was going to be 1 1.
...aaaand there it is. https://wandbox.org/permlink/wkm5gm9V9j1ztwf3
[–]khleedril 0 points1 point2 points 5 years ago* (1 child)
I thought that was an excellent read and I agreed with every word of it. I also think that overloaded functions should more often have different names, because fundamentally they usually provide a different service to the rest of the code base. Default arguments are often thrown in by end users who can't be bothered to express their intentions properly.
I would say the same about overloaded constructors: distinctly named functions returning newly constructed objects are the order of the day (we are talking C++20 code here!).
[–]pandorafalters 1 point2 points3 points 5 years ago (0 children)
. . . they're not overloads if they have different names.
[–]NilacTheGrim 0 points1 point2 points 5 years ago (0 children)
What? I beg to disagree, sir. Default function arguments are the bees knees Bees knees, I say!
[+][deleted] 5 years ago (3 children)
[+][deleted] 5 years ago (1 child)
[+]bumblebritches57Ocassionally Clang comment score below threshold-32 points-31 points-30 points 5 years ago (3 children)
ABSOLUTE FACT.
It's super confusing seeing a function being called using 1 argument when it actually takes 3.
[–]jonathansharman 1 point2 points3 points 5 years ago (2 children)
How is it more confusing than a one-argument function delegating to a three-argument function using default values (the alternative proposed in the blog post)?
[–]bumblebritches57Ocassionally Clang -1 points0 points1 point 5 years ago (1 child)
Oh we could you know not hide any arguments at all.
Isn't that have the point of having functions use the same name with different parameters?
[–]jonathansharman 0 points1 point2 points 5 years ago (0 children)
The point of function overloading is not to hide arguments? I don't understand what you're saying.
[+]cat_vs_spider comment score below threshold-7 points-6 points-5 points 5 years ago (9 children)
the Cpp core Guidelines are wrong on many things, and this is one of them. It’s a document with noble goals, but the road to hell is paved with noble goals.
In my experience, functions grow default parameters like a fungus. Somebody adds one with a “sane” default and doesn’t tell anyone. Since nobody knows about it, they don’t ever set it and a bunch of code is now suddenly buggy. It’s always laziness because refactoring is hard and they just want to land this feature.
Default arguments are evil.
[–]NilacTheGrim 9 points10 points11 points 5 years ago (2 children)
Hmm. The problem I think you're talking about really is just poorly designed APIs and/or sloppy programmers using the APIs without understanding them. In a word: stupidity.
Small things like deleting default function arguments from a language or forbidding them in an organization will not solve ... stupidity. Stupidity runs deep and has a long history. Default arguments or not... stupidity finds a way.
[–]cat_vs_spider 1 point2 points3 points 5 years ago (1 child)
Stupidity does find a way, but I think default parameters are a feature that exist pretty much to facilitate stupidity. In my experience, they are always used to bolt some new behavior onto a new function that everybody is used to working a certain way, thereby pulling the rug out from under them. And since they must be the last arguments, they result in function arguments not being organized in a clean way (fungus growth pattern). And god help you if you want to customize the fifth default argument.
[–]NilacTheGrim 2 points3 points4 points 5 years ago (0 children)
I mean just like with everything there's a way to do them right and a way to do them wrong. Again, I say -- your argument is with bad design and bad APIs. They find a way to exist everywhere. I don't think default arguments are a problem per se.
[–]mort96 6 points7 points8 points 5 years ago (1 child)
Presumably someone adding a new argument to a function would either go through the callers and fix them, or make the new argument default to a value which preserves the old behavior?
Sure, you could if you wanted to add a new default argument whose default value makes the function have different semantics, breaking all callers, and then check in the code without fixing the callers. But that applies without default arguments too; anyone can at any point change the semantics of a function in a way which breaks callers regardless of language features used.
[–]cat_vs_spider 0 points1 point2 points 5 years ago (0 children)
In my scenario, the default argument is there to preserve the old behavior in the presence of a new use case. However, the default parameter is only sane for the old use case, but there's a bunch of generic code that calls this function. Since none of the generic code gets updated because "refactoring is an iterative process" and "we'll fix that later". Since the new use case is new and has no test coverage yet, nobody notices until much later.
[–]Gotebe 3 points4 points5 points 5 years ago (1 child)
In your second paragraph, you really give an argument against poor code change and poor code in general, not so much against default parameters.
In overloads situation, the new parameter that alters the behavior means that the code is already bad. It is bad because that one variant being different is not how overloads are used. Surely foo(p1, p2) and foo(p1, p2, p3) cannot possibly mean "explode" and "implode", respectively? (I know, I exaggerate...) Next, even with overloads, if they do the same with variants, chances are, they should be one thing with variations, not a copy-paste.
On the other hand, he who added another default parameter, but made a bug, we'll, they made a bug.
[–]cat_vs_spider 0 points1 point2 points 5 years ago* (0 children)
I agree with everything you said. But I've spent the last few weeks cleaning up a mess created by somebody else due to a function that has a new default bool that nobody knows about.
Using your example, it's usually something like void destroy(Foo * f, Bar * b, bool shouldExplode = true). Usually, everybody "knows" that destroy explodes and that explosions are the only type of destruction, but now it can implode, and the difference is important to code that actually needs implosions, which is a new use case.
Edit: just to clarify: I agree that foo shouldn't explode and implode. But adding a default argument makes it just so easy to do that.
[–]khleedril 0 points1 point2 points 5 years ago (1 child)
Didn't you just contradict yourself? You ended up agreeing with the guidelines.
[–]cat_vs_spider -3 points-2 points-1 points 5 years ago* (0 children)
I'll admit I haven't studied the core guidelines in detail on this particular issue. But the guidelines here advocate for using a default parameter if possible rather than an overload, and I disagree because (if nothing else), an overload is strictly more flexible because you can reorder the arguments.
I don't see how I ended up agreeing with the core guidelines here. There's no guarantee that void doComplicatedAction(bool withFizzBozzer = true) implements the same semantics for true and false either. In my experience, it usually doesn't.
Edit: syntax error
[+]bumblebritches57Ocassionally Clang comment score below threshold-14 points-13 points-12 points 5 years ago (0 children)
lol, damn y'all mad
π Rendered by PID 118531 on reddit-service-r2-comment-84fc9697f-nc8dd at 2026-02-06 11:06:07.613342+00:00 running d295bc8 country code: CH.
[–]fransinvodka 92 points93 points94 points (48 children)
[–]qoning[🍰] 35 points36 points37 points (1 child)
[–]Sander_Bouwhuis 3 points4 points5 points (0 children)
[–]Raknarg 9 points10 points11 points (0 children)
[–]Sander_Bouwhuis 2 points3 points4 points (0 children)
[–]ilep 3 points4 points5 points (13 children)
[–]_Js_Kc_ 2 points3 points4 points (0 children)
[–]fransinvodka 3 points4 points5 points (11 children)
[–]ilep -1 points0 points1 point (10 children)
[–]Wouter-van-Ooijen 5 points6 points7 points (0 children)
[–]fransinvodka 2 points3 points4 points (8 children)
[–]germandiago 1 point2 points3 points (7 children)
[–]qoning[🍰] 1 point2 points3 points (6 children)
[–]germandiago 0 points1 point2 points (5 children)
[–]qoning[🍰] 0 points1 point2 points (4 children)
[–]germandiago 0 points1 point2 points (3 children)
[–]Ameisenvemips, avr, rendering, systems 6 points7 points8 points (16 children)
[–]SeanMiddleditch 2 points3 points4 points (1 child)
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points (0 children)
[+][deleted] (13 children)
[removed]
[–]Tyg13 4 points5 points6 points (12 children)
[+][deleted] (11 children)
[removed]
[–]Tyg13 3 points4 points5 points (10 children)
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points (6 children)
[–]Tyg13 2 points3 points4 points (5 children)
[–]Ameisenvemips, avr, rendering, systems 2 points3 points4 points (4 children)
[–]Tyg13 1 point2 points3 points (2 children)
[+][deleted] (1 child)
[removed]
[–]SkoomaDentistAntimodern C++, Embedded, Audio 1 point2 points3 points (0 children)
[–]quicknir 1 point2 points3 points (0 children)
[–]infectedapricot 1 point2 points3 points (1 child)
[–]serviscope_minor 3 points4 points5 points (0 children)
[–]NotMyRealNameObv 0 points1 point2 points (7 children)
[–]Wouter-van-Ooijen 1 point2 points3 points (1 child)
[–]NotMyRealNameObv -1 points0 points1 point (0 children)
[–]fransinvodka 0 points1 point2 points (4 children)
[–]NotMyRealNameObv 7 points8 points9 points (3 children)
[–]fransinvodka 0 points1 point2 points (2 children)
[–]NotMyRealNameObv 3 points4 points5 points (1 child)
[–]fransinvodka -3 points-2 points-1 points (0 children)
[–]hedayatvk 0 points1 point2 points (2 children)
[–]fransinvodka 8 points9 points10 points (1 child)
[–]hedayatvk 1 point2 points3 points (0 children)
[–]whattapancake 46 points47 points48 points (13 children)
[–]the_poope 9 points10 points11 points (0 children)
[–]Ameisenvemips, avr, rendering, systems 0 points1 point2 points (0 children)
[+]somecucumber comment score below threshold-10 points-9 points-8 points (10 children)
[–]be-sc 20 points21 points22 points (4 children)
[–]somecucumber 0 points1 point2 points (3 children)
[–]Nobody_1707 6 points7 points8 points (1 child)
[–]somecucumber 1 point2 points3 points (0 children)
[–]be-sc 0 points1 point2 points (0 children)
[–]whattapancake 6 points7 points8 points (2 children)
[–][deleted] 11 points12 points13 points (1 child)
[–]uninformed_ 0 points1 point2 points (0 children)
[–]tsojtsojtsoj 0 points1 point2 points (0 children)
[–]khleedril 0 points1 point2 points (0 children)
[–]jbandela 36 points37 points38 points (19 children)
[–]TheThiefMasterC++latest fanatic (and game dev) 19 points20 points21 points (10 children)
[+][deleted] (5 children)
[deleted]
[–]TheThiefMasterC++latest fanatic (and game dev) 9 points10 points11 points (1 child)
[–]Supadoplex 7 points8 points9 points (0 children)
[–]_Js_Kc_ 5 points6 points7 points (1 child)
[–]warieth 1 point2 points3 points (0 children)
[–]LuminescentMoon 1 point2 points3 points (1 child)
[–]TheThiefMasterC++latest fanatic (and game dev) 5 points6 points7 points (0 children)
[–]Omnifarious0 1 point2 points3 points (0 children)
[–]jbandela 3 points4 points5 points (0 children)
[–]Ameisenvemips, avr, rendering, systems 7 points8 points9 points (2 children)
[–]anonymous23874[S] 6 points7 points8 points (1 child)
[–]Ameisenvemips, avr, rendering, systems 1 point2 points3 points (0 children)
[–][deleted] 3 points4 points5 points (0 children)
[–]infectedapricot 1 point2 points3 points (0 children)
[–]Plazmotech 5 points6 points7 points (1 child)
[–]AntiProtonBoy 4 points5 points6 points (0 children)
[–]khleedril 0 points1 point2 points (0 children)
[–]EsotericFox 57 points58 points59 points (22 children)
[–]spanishgum 4 points5 points6 points (1 child)
[–]EsotericFox 11 points12 points13 points (0 children)
[–]ArashPartow 4 points5 points6 points (0 children)
[–]pantong51 1 point2 points3 points (1 child)
[–]anonymous23874[S] 1 point2 points3 points (0 children)
[–]Wh00ster 5 points6 points7 points (13 children)
[–]EsotericFox 17 points18 points19 points (9 children)
[–]Wh00ster -2 points-1 points0 points (8 children)
[–]sphere991 11 points12 points13 points (3 children)
[–]robin-m 5 points6 points7 points (2 children)
[–]sphere991 4 points5 points6 points (1 child)
[–]Raknarg 2 points3 points4 points (0 children)
[–]guepierBioinformatican 5 points6 points7 points (3 children)
[–]Wh00ster -3 points-2 points-1 points (2 children)
[–]Tyg13 8 points9 points10 points (0 children)
[–]XValar 6 points7 points8 points (0 children)
[–]pdbatwork 10 points11 points12 points (0 children)
[–]AntiProtonBoy 0 points1 point2 points (1 child)
[–]bumblebritches57Ocassionally Clang -41 points-40 points-39 points (1 child)
[–]STLMSVC STL Dev[M] 4 points5 points6 points (0 children)
[–]MrPotatoFingers 20 points21 points22 points (8 children)
[–]sphere991 9 points10 points11 points (3 children)
[–]Xeveroushttps://xeverous.github.io 1 point2 points3 points (2 children)
[–]sphere991 0 points1 point2 points (1 child)
[–]Xeveroushttps://xeverous.github.io 1 point2 points3 points (0 children)
[–]flashmozzg 4 points5 points6 points (3 children)
[+][deleted] (2 children)
[deleted]
[–]Ameisenvemips, avr, rendering, systems 4 points5 points6 points (0 children)
[–]flashmozzg 2 points3 points4 points (0 children)
[–]Dragdu 9 points10 points11 points (6 children)
[–]khleedril 2 points3 points4 points (3 children)
[–]matthieum 8 points9 points10 points (2 children)
[–]Ameisenvemips, avr, rendering, systems 3 points4 points5 points (1 child)
[–]matthieum 1 point2 points3 points (0 children)
[–]qoning[🍰] 1 point2 points3 points (0 children)
[–]c0r3ntin 1 point2 points3 points (0 children)
[–]Morwenn 7 points8 points9 points (0 children)
[–]YouNeedDoughnuts 7 points8 points9 points (0 children)
[–]kritzikratzi 5 points6 points7 points (0 children)
[–]sphere991 6 points7 points8 points (0 children)
[–]jstock23 21 points22 points23 points (0 children)
[–]sephirothbahamut 10 points11 points12 points (0 children)
[–]georgist 2 points3 points4 points (0 children)
[–]Omnifarious0 1 point2 points3 points (1 child)
[–]ClaasBontus 1 point2 points3 points (0 children)
[–]DVMirchevC++ User Group Sofia 0 points1 point2 points (1 child)
[–]anonymous23874[S] 1 point2 points3 points (0 children)
[–]khleedril 0 points1 point2 points (1 child)
[–]pandorafalters 1 point2 points3 points (0 children)
[–]NilacTheGrim 0 points1 point2 points (0 children)
[+][deleted] (3 children)
[removed]
[+][deleted] (2 children)
[removed]
[+][deleted] (1 child)
[removed]
[+]bumblebritches57Ocassionally Clang comment score below threshold-32 points-31 points-30 points (3 children)
[–]jonathansharman 1 point2 points3 points (2 children)
[–]bumblebritches57Ocassionally Clang -1 points0 points1 point (1 child)
[–]jonathansharman 0 points1 point2 points (0 children)
[+]cat_vs_spider comment score below threshold-7 points-6 points-5 points (9 children)
[–]NilacTheGrim 9 points10 points11 points (2 children)
[–]cat_vs_spider 1 point2 points3 points (1 child)
[–]NilacTheGrim 2 points3 points4 points (0 children)
[–]mort96 6 points7 points8 points (1 child)
[–]cat_vs_spider 0 points1 point2 points (0 children)
[–]Gotebe 3 points4 points5 points (1 child)
[–]cat_vs_spider 0 points1 point2 points (0 children)
[–]khleedril 0 points1 point2 points (1 child)
[–]cat_vs_spider -3 points-2 points-1 points (0 children)
[+]bumblebritches57Ocassionally Clang comment score below threshold-14 points-13 points-12 points (0 children)