Constexpr and consteval are hacks that 1) should have just been the default, and 2) shouldn't even be on the function definition, it should instead have been a keyword on the usage site: (and just use const)
int f() { ... } // any old regular function
const int x = f(); // this is always get evaluated at compile time, (or if it can't, then fail to compile)
int y = f(); // this is evaulated at runtime
That would be the sane way to do compile time functions. const int x = f(); // this is always get evaluated at compile time, (or if it can't, then fail to compile)
That's very silly. You're saying this should fail to compile? void foo(int x) {
const int y = bar(x);
}
There's no way the compiler can run that, because it doesn't know what x is (indeed, it would have a different value every time you run the function with a new argument). So your proposal would ditch const completely except in the constexpr case, everything runtime would have to be mutable.So you respond "well, I didn't mean THAT kind of const, you should have a different word for compile-time constants and run-time non-mutability!" Congratulations, you just invented constexpr.
There are many bad things about C++, but constexpr ain't one of them.
Yeah, I see no problem with that. Non-constant expressions usage of 'const' has always just seemed like a waste of time for me, never found it useful. But I guess a lot of people really liking typing const and "preventing themselves from accidentally mutating a variable" (when has that ever happened?), so as a compromise I guess you can have a new keyword to force constant expressions:
constexpr auto x = foo(); // always eval at compile time
const auto x = foo(); // old timey const, probably runtime but maybe got constant folded.
but it's not really a big deal what they keyword is, the main point was that "give me a constant value" should be at the usage site, not at the function definition.The issue is, not everything can be done at compile time, and so “I can use this at compile time” becomes part of the signature because you want to ensure that it will continue to be able to be used that way. Without it, changes in your function could easily break your callers.
This isn't about "oops, I didn't mean to mutate that." This is about rapidly being able to reason about the correctness of some code that is leveraging const-qualified code.
I can't count the number of times I've seen someone new to the language use map::operator[] without realizing that it's a mutating operation.
for (const auto &thing: collection) {
counts[thing]++;
}
Works nicely, you don't have to check if it's already there before ++ing it. As long as you know that's what operator[] does, it comes in handy more than I would've expected.Another popular use case is something along the lines of:
std::unique_ptr<T>& ptr = ptrs[key];
if (ptr != nullptr) {
ptr = std::make_unique<T>(...);
}
That said, I don't know what behavior I'd want if maps didn't automatically insert an element when the key was absent. UB (as with vector)? Throw an exception? Report the incident to Stroustrop? All these options feel differently bad.Maybe it's not a concern in C-family languages, but rust's culture of defaulting to let and only using mut when it's specifically required does feel very pleasant and ergonomic when I'm in that headspace.
to me, pretty much a few times per week at least
Move constructors are not needed, they don't solve a 'problem', but improve on previous semantics.
So in C++ "const x = EXPR" would make sense to request compile-time evaluation, but in C it wouldn't.
for (const auto item: vec) { ... }
`item` is not a compile-time constant. It's different every run of the loop.It's been so long since I used C++ for serious work that we weren't using C++11, so neither auto nor range-for were available. It would be uncommon to see "const type = " with a non-reference type and a non-constant initialiser.
Even with your example, some styles avoid "const auto item", using either "auto item" or "const auto& item" instead, because the "const" matters when taking a reference, not so much with a copy.
But I appreciate your point applies to const variables with non-constant initialisers in general, in the language.
There was once a big deal in literature about const in C++ being the "better" alternative to how #define is commonly used with C for constant values, and it seemed applicable to the thread as a key distinction between C and C++, which the parent commenter seemed to have conflated by mistake.
But I'd forgotten about const (non-reference) variables accepting non-constant initialisers, and as I hadn't used C++ seriously in a while, and the language is always changing, I checked in with a couple of C++ tutorials before writing. Unfortunately those tutorials were misleading or too simple, as both tutoruals said nothing about "const type x = " (non-reference/pointer) being uwed in any other way than for defining compile-time constants.
It's bit embarrssing, as I read other parts of the C++ standard quite often despite not using it much these days. (I'm into compiler guts, atomics, memory models, code analysis, portability issues, etc.). Yet I had forgotten this part of the language.
So, thanks for sending me down a learning & reminder rabbit-hole and correcting my error :-)
Constexpr and auto have nothing to do with macros.
The older version is being improved, especially for ergonomics. Regarding your examples, ranges do not obsolete iterators, they are just a convenient way to pass around iterator pairs, but actual range are better implemented in terms of iterators when they are not just a composition of ranges. Similarly move semantics has little to do with nrvo (and in fact using move often is suboptimal as it inhibits nrvo).
Again, I have no idea how constexpr and auto have anything to do with macros.
Concepts fix implicit template requirements
> What about ranges?
Fix the bad iterators
> Auto
Fix the the overly long type names
> Move semantics
This is mostly necessary because of excessive copies that cpp does
> Consteval
Fix cases where constexpr couldn't do it at comptime
Move is fixing the problem of unnecessary mass-construction when you pass around containers.
std::ranges was introduced because dear fucking god the syntax for iterating over a partial container. (And the endless off-by-one errors)
concepts, among other things, fix (sorta) the utter shit show that templates brought to error messages, as well as debacles like SFINAE and std::enable_if.
You're right. They're not fixing problems created by previous features. They're all fixing problems created or made massively worse by templates.