Template specification used to need a lot of decoration but with auto most of that is gone.
Template specification used to need a lot of decoration but with auto most of that is gone.
For example, a few areas where Swift reduces boilerplate and the syntax it provides to do so:
- a complete null/optional story: first class optional types (T? = Option<T>), null chaining (obj?.prop), and null coalescing (T ?? "default")
- error handling: fn() throws Err (= fn() -> Result<T,Err>), try, try?, try!, do/catch (out-of-band error handling), w/o stack unwinding, see nulls
- and struct/enum lifecycle management: constructors, getter/setter props, defaults, named functions, named tuples, etc. (this list is loong)
Individually each feature is small and could possibly be argued as bloat or overhead since you can accomplish the same with just regular functions and Rust-like code and abstractions, but combined they make the language feel more expressive (and more fun IMO) than would otherwise be possible and often without sacrificing performance. They also serve to reduce cognitive overhead in the same way using for loops is nicer than desugaring to while loops. This is nothing to say of the syntax itself, and a bunch of other small features; Swift really is a case where the whole is greater than the sum of of its parts.I would describe dealing with those issues as "boilerplate" because it's just a lot of code you should not have to write.
Swift is so much more comfortable and fun.
(btw I still love C++ and cross platform support completely flips the script)
I'm not sure we're thinking of the same standard library if you would describe it as thin...[0]
[0]: https://docs.microsoft.com/en-us/cpp/standard-library/cpp-st...
I honestly don't know what else you would be looking for in a standard library. C++ provides out of the box:
- hash maps
- sets
- dynamic arrays
- fixed length arrays
- bit sets
- option types
- atomics
- mutexes
- futures
- strings
- queues/stacks/Deques
- tuples
- variants
- streams
- etc
In addition, they have a huge range of algorithms that operate on all of these containers. I'm really curious, what does the standard library not have that you think forces you to write boilerplate?
- Unified result types. C++23 is finally going to have `std::expected`, but it will probably take another version or two to see the `std::expected`-only version of standard library.
- Iterator utilities. Again, C++23 will see a significant improvement in <ranges> but that still falls short of what Rust `Iterator` provides by default.
- Checked arithmetic. Rust and many other languages distinguish checked and unchecked arithmetic and put one of them (typically unchecked one) into the standard library. C++ doesn't have anything like that.
- Guarded mutex (that is, a portion of memory by design only accessible when locked) and concurrent queue. C++ does support concurrency out of the box, but its tools are pretty bare bones and this is one example.
Just a couple other points though, C++ provides fully customizable iterators. They don't have ranges, but ime the amount of times I need ranges vs just iterating over a container are relatively low. Also, is a lock guard in C++ not the same thing as a guarded mutex? This may be ignorance on my part because I'm not familiar with rust, but it sounds like the same thing to me. A lock guard will lock and automatically release a mutex once it goes out of the current scope.
What I find most lacking from the C++ standard library is actually coherence. Different parts of standard library were developed separately from each other, and while adapters between them exists (e.g. stream iterators can be used with `std::format_to`) they are by definition boilerplates. Some parts are simply not relevant in the modern C++ programming and still have to be retained for compatibillity (e.g. `std::auto_ptr`). In this view C standard library is actually a lot more coherent than C++, even though C standard library itself is less powerful and featureful than C++.
> They don't have ranges, but ime the amount of times I need ranges vs just iterating over a container are relatively low.
I think this is simply because C++ iterators are less usable. All iterators can be technically rewritten as loops, so you may have internalized to avoid using iterators when iterators would be beneficial but difficult to use. I personally find myself using iterators a lot more in Rust than in C++.
> Also, is a lock guard in C++ not the same thing as a guarded mutex? This may be ignorance on my part because I'm not familiar with rust, but it sounds like the same thing to me. A lock guard will lock and automatically release a mutex once it goes out of the current scope.
I actually don't know the exact term (the name "guarded mutex/lock" was what I've used in the past), but I meant a mutex that is strongly coupled with some additional data so that the API prevents or at least discourages to access the data without acquiring the mutex. In a hypothetical design `std::mutexed<T>` will be `T` plus `std::mutex` and `std::lock_guard<std::mutexed<T>>` would have `operator *` which allows the access to `T`. In Rust [1] this is actually the default, and you never have a bare `std::mutex` (though it can be simulated with placeholder data, `Mutex<()>`).
Gotcha, I appreciate the back and forth which helped me to clarify that point a bit better :)
> What I find most lacking from the C++ standard library is actually coherence.
I 100% agree. There are plenty of areas that C++ is very lacking, and I think this comment highlights that.
I initially started this thread off because a big complaint I hear among C++ users is the language is a bloated mess, which is why the parent comment saying that C++ had a thin standard library didn't ring true to me. But I think what you're saying here is closer to the truth. It has a lot of good features, but they're mostly after thoughts and poorly integrated. Whereas a language like Rust (or another modern language) has around the same amount of features builtin, but they're more focused, integrated, and planned :)
What should clearly be outside is stuff like graphics library, GUI, HTML.
C++ does provide all of this in the standard library except for sockets, Json and XML. I think once you get to things like Json and XML it doesn't make much sense to include that in the standard library. There are plenty of applications that don't need or use either of those, and it's a shame to bundle some configuration language when somebody may have another preference like yaml or something. There's nothing wrong with providing additional standard add-ons that include these, but I definitely don't think it makes a lot of sense to bundle it in with the standard.
This is a whole separate conversation, but my main point was there are a lot of better things to pick on C++ for. Most complaints I hear are that it includes too many useless features haha.
The streams from the standard library are broken beyond repair, I avoid them at all costs: https://www.moria.us/articles/iostream-is-hopelessly-broken/ Even <stdio.h> C header is much better abstraction, despite 50 years old.
To be useful, option types and tuples need language support. C++ doesn't have it.
Similarly, to be useful, futures need runtime support i.e. asynchronous I/O. They also need language support like async-await, to generate these state machines. C++ has none of them.
The standard library still does not really support Unicode, people need third-party libraries like ICU just to manipulate strings: https://stackoverflow.com/q/42946335/126995
I suspect he just wanted to learn Swift and that's a good reason enough to make this rewrite, as he admits in the final paragraphs of the article: "I’ve enjoyed learning Swift and am much happier with the state of the code now"
C++:
int first[] = {1,2,3,4};
int second[] = {5,6,3,4};
std::vector<int> v(8);
std::vector<int>::iterator it;
std::sort (first,first + 4); // 1 2 3 4
std::sort (second,second + 4); // 3 4 5 6
it = std::set_union (first, first + 4, second, second + 4, v.begin());
v.resize(it - v.begin());
for(it = v.begin(); it != v.end(); ++it)
std::cout << ' ' << *it;
std::cout << '\n';
Swift: let a = Set([1,2,3,4])
let b = Set([5,6,3,4])
let c = a.union(b)
print(c) //prints "[1, 2, 3, 5, 6, 4]" std::set a{1,2,3};
std::set b{5,6,3,4};
std::set c{a};
c.insert(b.begin, b.end());
for(auto x: c)
std::cout<<c<<' c;
std::cout << '\n'; #include <algorithm>
#include <iterator>
#include <vector>
#include <iostream>
using namespace std::ranges;
int main()
{
std::vector a = {1,2,3,4};
std::vector b = {5,6,3,4};
sort(a);
sort(b);
decltype(a) c;
set_union (a, b, back_inserter(c));
copy(c, std::ostream_iterator<int>(std::cout, "\n"));
}
https://gcc.godbolt.org/z/44Ksf1hK8Note the value type deduction when declaring the vectors, the use of back_inserter to avoid having to resize the result (c) vector, the use of std::ranges to do away with all the begin + end crapola, and the way ranges and iterators are inter-mixed.
C++ is always going to be a bit more verbose in trivial cases, but it's not that bad, and exposes a lot more flexibility in complex ones.