6 ms·
I also find that, in C++, int sum = 0; for (int i = 0; i < n; ++i) sum += x[i]; return sum is a lot easier to understand than return
by porphyra 2y ago
I also find that, in C++,
int sum = 0;
for (int i = 0; i < n; ++i)
sum += x[i];
return sum
is a lot easier to understand than
return std::accumulate(x.begin(), x.end(), 0, [](int a, b) {return a + b;});
Yet, the latter is considered more correct and better, with static analysis like cppcheck telling you to use the latter. It does have many advantages, like no mutable variables lying around, but gee it is annoying to read.
- constantcrying 2y agoThat is just a question of what you are used to. The "functional style" example is very obvious to a person who is familiar with that style. In fact if the rest of the code base is in a similar style it is the easier one to understand. There is nothing inherent which makes either better or cleverer than the other. (Also the first one is incomplete, as "n" is not defined, making the later more self contained)
- haolez 2y agoI like C++, but I don't understand this tradition of keeping these annoying namespaces prefixed to everything. Just get rid of those std::, foo::bar::whatever::, etc from your code and make it more readable. Use the "use" clause. It's very rare for such names to be ambiguous in the same file, unless I'm missing some bigger picture here.
- porphyra 2y agousing namespace std; is considered kinda bad since you don't want your namespace to be polluted with a bunch of std stuff. For example if you have `int count` lying around somewhere you'd want to be able to call `std::count` without fear of it being shadowed.
- haolez 2y agoBut ambiguity gives you readability. And if a "count" variable clashes with the function name, it's trivial for the compiler to catch this and warn you. It's a matter of preference, but I'd still risk ambiguity and name collision, especially when you go beyond std:: (like boost stuff).
- devnullbrain 2y agoIt gives you readability. It's not what I'm used to, so it feels like reading French to me. Generally, if I see something without a namespace, it has a local scope.
- tialaramex 2y agoBecause of bad design decisions, a whole pile of unrelated stuff lives in the std namespace, and the disambiguation probably isn't what you wanted, so this can silently cause surprises.
- quotemstr 2y agoOne pet peeve of mine is people using std::for_each instead of a simple loop
- jraph 2y agoSame in JS. I once worked with someone who would use [].forEach and didn't like for loops because functional good, iterative loops bad. It was essentially in the coding style of the project and the for version would be rejected in MRs. I don't miss this. IMHO [].forEach just essentially expresses the same thing as a regular for loop, although in a more verbose and convoluted way that is also likely very inefficient because a JS compiler probably can't optimize away the function call per iteration, because of the very dynamic nature of JS. forEach probably has its use when you specifically need to call an existing function on each element, but I don't otherwise see the appeal.
- lupire 2y agoYou are making a strong evidence-free claim about what JS compilers don't do. https://stackoverflow.com/questions/9981607/array-foreach-running-faster-than-native-iteration-how https://stackoverflow.com/questions/9981607/array-foreach-ru...
- Teckla 2y agoAt $WORK we use Mozilla Rhino for some JavaScript processing. We tested the normal loop vs. the clever loop. Performance-wise, the normal loop blew the doors off the clever loop.
- MrJohz 2y agoRhino is a very uncommon engine though. It has its niche, but if you're developing for simpler engines like that, then you typically know that in advance and know what considerations you're going to need to make for that. When it comes to performance, lessons that apply to Rhino are unlikely to apply to more mainstream engines. EDIT: That's not to say that everyone should use map and forEach all the time, just that your benchmarks are unlikely to be relevant to most JS devs outside of your specific use-case.
- a_t48 2y agoWe now have return std::reduce(x.begin(), x.end()); Which is a little cleaner and is even faster (compiler is free to do the additions in any order). https://en.cppreference.com/w/cpp/algorithm/reduce https://en.cppreference.com/w/cpp/algorithm/reduce - it looks like even the `accumulate` example can be made simpler with `std::plus`. I prefer the `reduce` option for a number of reasons, but understand why someone might not.
- autoexecbat 2y agoI think most people would be thinking "hmm now I need to read what 'reduce' mean in this context"
- a_t48 2y agoFor at least the first time, yeah.
- CuriousCosmic 2y agoAt least in my experience in the C++ world there is a general expectation that you learn the "standard algorithms" and prefer them over the alternatives unless you have a specific reason not to. For example: outside the C++ ecosystem, most people probably would stare blank faced if they saw `std::rotate` in a codebase but it's basically a meme at this point in the C++ space. Ex: https://www.youtube.com/watch?v=UZmeDQL4LaE https://www.youtube.com/watch?v=UZmeDQL4LaE
- j1elo 2y agoLook at that link: 2,4,6) ... These overloads participate in overload resolution only if std::is_execution_policy_v<std::decay_t<ExecutionPolicy>> is true. std::is_execution_policy_v<std::remove_cvref_t<ExecutionPolicy>> is true. std::is_execution_policy_v? std::decay_t? std::remove_cvref_t? Really, this madness with C++ needs to stop. People who work on newer versions of the language might be very conservative with the language syntax itself, but they clearly decided having carte blanche to adding an infinite amount of stuff to the standard library. Could someone please force them to stop, somehow? The whole rest of the industry would be thankful.
- neonsunset 2y agoC++ makes this (and many other things!) needlessly painful. In C# it is just return numbers.Sum();
- tialaramex 2y agoWhere did "numbers" come from, and why are you so sure you can Sum() it? The original C code offered has some data structure (perhaps an array?) called x. Do C# arrays have a Sum method? I don't think so. In Rust you would probably just write: x.iter().sum()
- neonsunset 2y agoC# arrays don't. However, C# arrays are IEnumerable<T>, which does.
- lupire 2y agoIEnunerable<T> of course does not have Sum. Only collections of numeric types (including arrays) have Sum. https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.sum?view=net-8.0 https://learn.microsoft.com/en-us/dotnet/api/system.linq.enu...
- deleted 2y ago[deleted]
- masfuerte 2y agoSo you can't use IEnumerable<T>.Sum if T is a custom type implementing operator+ ?
- deleted 2y ago[deleted]
- neonsunset 2y agoYou could, but .Sum is a little more specific than that - it is non-overflowing sum. Historically, constraining generic arguments on addition was problematic - the full feature set of numeric types was "lifted" to be fully representable through generics only recently[0]. With that said, there is an open proposal[1] to introduce additional generic math overloads to IEnumerable<T> methods, but it hasn't seen much activity as the existing overloads cover most commonly used numeric types already. [0]: https://learn.microsoft.com/en-us/dotnet/standard/generics/math https://learn.microsoft.com/en-us/dotnet/standard/generics/m... [1]: https://github.com/dotnet/runtime/issues/64031 https://github.com/dotnet/runtime/issues/64031
- sgarland 2y agoThat is somewhat annoying, yes. I'm a huge fan of Python's list comprehension, but it's generally accepted to be a normal part of the language, and IMO, more readable: print([x for x in range(10) if not x % 2]) [0, 2, 4, 6, 8] vs. l = [] for x in range(10): if not x % 2: l.append(x) print(l) [0, 2, 4, 6, 8]
- psychoslave 2y agoWith ruby 3.4 (prior to that `_1` should be used instead of `it`): (0..10).select{it.even?}
- psychoslave 2y agoAlso not that the result is `[0, 2, 4, 6, 8, 10]`, unlike the behavior of Python which exclude the value explicitely passed in parameter from the represented range.
- okanat 2y agoCall me crazy, but I find the second example more readable. The first example is long line and you have to go back and forth to understand it. The second example is a single top down pass.
- crazygringo 2y agoAgreed, me as well. The second example reflects how my brain thinks and the intuitive order of what has to happen. In the first example, it's all out of order.
- sgarland 2y agoI’m sure I’m biased, since I code mainly in Python. List/dict comprehensions read the same to me as the longer form, just more concisely. This can be taken too far, and you can wind up with horribly obtuse one-liners that are just awful.
- 2y ago
- Xeoncross 2y agoint sum = 0; for (int i = 0; i < n; ++i) sum += x[i]; Pretty much looks the same in all C-like languages. I've written that in Java, Go, TypeScript, PHP, etc... On the other hand, that second 'clever' example always looks different for every stupid language. It's std::accumulate in C++, streams in Java, list comprehension in python, etc... Clear is better than clever.
- floxy 2y agoOne man's clever is another man's clear. https://web.archive.org/web/20180619022832/http://webdocs.cs.ualberta.ca/~smillie/Jpage/MyLife.pdf https://web.archive.org/web/20180619022832/http://webdocs.cs... "The instructions corresponding to a conventional language might be expressed something like the following: Select an apple from the box. If it is good, set it aside in some place reserved for the good apples; if it is not good, discard it. Select a second apple; if it is good put it in the reserved place, and if it is not good discard it. ... Continue in this manner examining each apple in turn until all of the good apples have been selected. In summary, then, examine the apples one at a time starting with the first one we pick up and finishing with the last one in the box until we have selected and set aside all of the good apples. On the other hand the instructions corresponding to an array language could be stated simply as “Select all of the good apples from the box.” Of course, the apples would still have to be examined individually, but the apple-by-apple details could be left to the helper. Conventional programming languages may be considered then “one-apple-at-a-time” languages with machine languages being a very primitive form, whereas array languages may be considered “all-the-apples-at-once” languages. In two of the following three sections we shall consider the array languages APL and its “modern dialect” J with an intervening section giving a short discussion of the array language Nial which was influenced by APL."
- bsza 2y ago> Pretty much looks the same in all C-like languages Because it’s ancient; that doesn’t necessarily make it better or clearer. Look at it like you’ve never seen it before. It has an entire line of boilerplate smack bang in the middle. Sure, your brain filters it out because you’re used to it, but it’s still fugly. And it’s prone to typos/copypaste errors like all boilerplate.
- sesuximo 2y agoImo it doesn’t matter at all. Both are fine. Surely there are more pressing things out there to think about!
- lupire 2y agoWhy would you not use std::sum(x) ?
- phailhaus 2y agoThe other thing nice about the second style is that it's much easier to skim when part of a larger function, since it's a single type-checked statement. Keeps you from doing too much in it too.
- kalkr 2y agoTraditional C style for is terrible compared to what you can do with iterators in my opinion, but idk if C++ has those. list.iter().sum() ezpz
- williamcotton 2y agoI haven’t written any C++ since the 90s. That is unrecognizable. Is that an anonymous function?
- floxy 2y agoYes. https://learn.microsoft.com/en-us/cpp/cpp/lambda-expressions-in-cpp?view=msvc-170 https://learn.microsoft.com/en-us/cpp/cpp/lambda-expressions...
- nine_k 2y agoC++98 should be forgotten as a bad dream. C++17 is almost a sane and convenient language (as long as you remember about the boiling depths into which you can fall if you're careless). C++ will live for very long, like Fortran. And also like Fortran, there now must be a serious and uncommon reason to start a new project in it.
- pklausler 2y agoWhy does one need an uncommon reason to begin development in Fortran?
- nine_k 2y agoFor instance, the much narrower circle of experts. Usually if you do need Fortran, it means you're going to run numerical code on a huge cluster with RAM in terabytes and cores in the thousands, so the developers you're looking for should understand parallelization very well (and how modern Fortran does it), know words like MPI and Slurm, etc. Beside that, knowledge of the common numerical methods and libraries is expected. This is a relatively unusual setup. Few job listings mention it. If you just want some highly parallel numerical code, but a GPU with several tens of gigs of RAM would suffice, you just take Numpy, or PyTorch, or other such library, wrapped into Python. You suddenly have a wide circle of developers, plethora of references, and no lower chances to publish in a prestigious journal than if you'd taken Fortran :)
- nine_k 2y agoIt's mostly the problem of the language, and not of the approach. In a more expressive language you can omit the explicit slicing (x.begin(), x.end()) and have the compiler derive the lambda's signature for you, so you'd write something like reduce(x, (a, b) => a + b), or even fold (+) x, with all the same static analysis and efficient compilation guarantees.
- knome 2y agoI think it's mostly a fad issue. Normal loops and if statements are just as possible to hit with a static analyzer. But the very fact that they look easy makes a certain kind of programmer see them as beneath them. They want the complex looking code, even if it's functionally equivalent and semantically no more sound. They like the visual noise and complexity of it. It rubs their egos the right way.
- nine_k 2y agoLoops are more complex. They expose more implementation details, worse, they "expose" irrelevant details. Unless your CPU is very simple, like a Cortex M0, the C compiler will likely rewrite your loop using vector instructions (think MMX / SSE / Neon), leaving an unrecognizable mess where a neat loop with an index used to be. C was invented to match PDP-9 and PDP-11, and it matches them beautifully. Constructs like *a++ = *b++ directly compile to instructions like MOV (R1)+, (R2)+, etc. But however much I may like the beauty and simplicity of the PDP-11 architecture, it belongs to the past, or maybe to simplest MCUs. C no longer matches hardware all that well, and especially the idioms of C from classic books written 30-40 years ago don't match modern hardware woefully, if you care about the last bit of performance. (If you don't, take C#, Java, Go, even V8; they are plenty fast with JIT compilers.)
- knome 2y ago00106 template<typename _InputIterator, typename _Tp, typename _BinaryOperation> 00107 _Tp 00108 accumulate(_InputIterator __first, _InputIterator __last, _Tp __init, 00109 _BinaryOperation __binary_op) 00110 { 00111 // concept requirements 00112 __glibcxx_function_requires(_InputIteratorConcept<_InputIterator>) 00113 __glibcxx_requires_valid_range(__first, __last); 00114 00115 for (; __first != __last; ++__first) 00116 __init = __binary_op(__init, *__first); 00117 return __init; 00118 } https://gcc.gnu.org/onlinedocs/libstdc++/libstdc++-html-USERS-4.0/stl__numeric_8h-source.html#l00108 It's just a loop. The compiler is doing all of the same transformations regardless of whether you use the higher order function or just write a loop yourself. -------------- _STD_BEGIN _EXPORT_STD template <class _InIt, class _Ty, class _Fn> _NODISCARD _CONSTEXPR20 _Ty accumulate(const _InIt _First, const _InIt _Last, _Ty _Val, _Fn _Reduce_op) { // return noncommutative and nonassociative reduction of _Val and all in [_First, _Last), using _Reduce_op _STD _Adl_verify_range(_First, _Last); auto _UFirst = _STD _Get_unwrapped(_First); const auto _ULast = _STD _Get_unwrapped(_Last); for (; _UFirst != _ULast; ++_UFirst) { #if _HAS_CXX20 _Val = _Reduce_op(_STD move(_Val), *_UFirst); #else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv _Val = _Reduce_op(_Val, *_UFirst); #endif // ^^^ !_HAS_CXX20 ^^^ } return _Val; } https://github.com/microsoft/STL/blob/63354c3fa9c1fb2ab1fccb58c47d23c6af1c290f/stl/inc/numeric#L24 Also just a loop in MS' standard lib.
- GrantMoyer 2y agoIf you use C++23's ranges and Boost Lambda and sacrifice your better judgement, you can get it down to: fold_left(x, 0, _1 + _2)
- leetcrew 2y agothe readability issue here is in part that function arguments are purely positional in c++. a little over the top for this simple example, but you can always create local variables to document intention. auto initVal = 0; auto accumulateOp = [](int a, b) {return a + b;}; return std::accumulate(x.begin(), x.end(), initVal, accumulateOp); I'd prefer to see a for each loop over algorithms functions in such simple cases, but I'd prefer almost anything over direct indexing when it isn't necessary. when I see that in new code, my first thought is always "what am I missing here?".
- globular-toast 2y agosum(x) is easiest to read. If your language doesn't provide that as standard just define it yourself. In general you should always be idiomatic, though. If every C++ developer understands the accumulate version then use that.