Back in 2010, I read the c++faq religiously, tried to warp my projects to it in college, and in the end produced nothing because i was too busy trying to be a big boy. Now, the c++faq is obsolete. I'm generally tired of opinionated lists of "best practices," code that actually works is 10x better than what some guy (always a guy) somewhere thinks I should do whereas in fact what I should do is actually build things that I can use.
Caveat (big one) okay, it's not "some guy", the authors are literally Bjarne Stroustrup & Herb Sutter. I still feel anxiety though.
You should probably read it before ranting against it assuming you already know the general gist of what it says. Or at least read the introduction on what the rules are trying to be and, just as importantly, what they are not trying to be.
The authors of those guidelines are Bjarne Stroustrup & Herb Sutter, who kinda have more than average C++ experience. The guidelines are generally very well reasoned with motivating examples & practical exceptions to the rules. They are generally not of the opinionated "style guide" or "ban these things" camp that a typical set of "guidelines" tends to fall into.
I think the only CppCoreGuideline rule I disagree with is it wants to use T& for out params whereas I prefer T* so that at the call site there's a more obvious indicator of potential mutability.
I know I am in the outlier here but I very much prefer the second way of writing code. Sure the first one gives more compile-time safety but as someone who has to read the code, I have to go through an additional level of indirection before I truly understand how Month is implemented.
Others might offer a counterpoint that hiding such implementation details is exactly the point of OOP. True but as a developer who has to read and fix code, I do have to worry about the implementation details because the bugs could be in implementation details and hiding simple integers behind an extra layer of types adds to one more step when I am trying to reach the implementation details and fix bugs there. Multiply this experience by the number of all the layers in the code and maybe others might be able to appreciate why I prefer the second type of coding. Please someone tell me I am not the only one who feels this way!
> I have to go through an additional level of indirection before I truly understand how Month is implemented
I'd just hover over it in my IDE and it'd tell me what it is. Regardless, "Month" tells me it's a "month" whereas "int" tells me "lol get fucked I ain't telling you shit"
The "do" example there is much more readable than the "don't" example. Especially in any further usages of it, as it's one less thing I need to keep track of. That is, I don't need to second guess if the variable was passed to the wrong thing since the compiler is validating that for me. I don't need to track that during my "reading"
> True but as a developer who has to read and fix code, I do have to worry about the implementation details because the bugs could be in implementation details
Then why are you arguing against it? A "Month" type cleanly restricts the implementation details to a specific controllable set, which an "int" does not and is a free for all. You're in for a nightmare trying to fix people doing math with dates if you use "ints", whereas you can pretty easily fix all of the users of a "Month" or "Date" type by just removing the math operators (or "fixing" them to work appropriately) and letting the compiler spot all the mistakes.
Note, the person you're replying too is talking about fixing code, not using code. A mouse-over tip will tell you what it is. If code is broken due to it being bad abstractions, you will need to know how it is implemented regardless.
And like below someone retorting below "hurr that's just bad code then" well all code is bad. Some people just seem to think their own machines will think for them or even worse, some other developer thought for them and didn't make any mistakes. Others realize people are infallible and you need installed hatches in order to get in there and fix things, and they prefer systems where it is easier to do so.
I feel like the big exception to this are very large libaries which can hire multiple SV-salaried engineers to work on them full-time (STL, Qt, etc), because yes, they likely might have done a good job. But some in some small shop C++ code, the constraints are different, the expected correctness is different, and you just can't trust that people have done everything right and it helps to have hatches.
Anyway, there are other issues with a Month type but other than that issue, the use of a const accessor method is yes better than public member, so I don't fully agree with them that the 1st guideline is that bad, I certainly agree with the spirit of it to a degree. I skimmed the guide and a lot of it is pretty good. Like C.2 really jives with me and gives me a very good rule that seems to explain why a lot of OOP code rubs me the wrong way, of which encapsulating a bunch of disparate state with no real invariants or constraints makes no sense but is something you see from a lot of code out there. That said, I still will never follow anything religiously, even if Bjarne Stroustrup wrote it because really no rules fit every situation and devs should be wary when people make pronouncements of rules that are total and fixed.
> If code is broken due to it being bad abstractions, you will need to know how it is implemented regardless.
Sure, but the type (and thus the guideline) is still helping with that instead of hurting. I can much more easily find all usages of a broken `Month` type than I can an int representing a month which could be named anything.
> That said, I still will never follow anything religiously, even if Bjarne Stroustrup wrote it because really no rules fit every situation
Oh definitely, I don't agree/follow all of those guidelines either. The guidelines themselves don't pitch themselves as that, and many of the "rules" have whole sections listing reasonable exceptions and why those exceptions are OK.
> Regardless, "Month" tells me it's a "month" whereas "int" tells me "lol get fucked I ain't telling you shit"
I disagree. Is "Month" a 1-based month ordinal? A 0-based month index? A string containing user input that should be preserved in it's original form to avoid data loss (be that "jan", "JAN", "January", "1", ...)? Memoized? An indirect reference to OCR data? Do I even have a guarantee it's on the Gregorian calendar, or am I in the context of some calendar app that might support chinese lunar calendars? Is it JSON serializable? Is it a localization placeholder? Is it an integer typedef that means different things in different contexts, including "months ago" in some relative timestamp formatting code?
"Month" tells me jack shit that the function and variable names didn't. "int" at least tells me I can math it, and that if I'm dumb enough to have memcpy-serialized code, I have a potential porting hazard in the form of endian issues - and size variance if I'm dumb enough to target an ILP64 platform. Based on personal experience, these are far more likely bugs for me to encounter and need to fix than any that would be caught by stronger month typing. There is a place for stronger types - I'm not complaining about Rust's SystemTime/Instant/Duration trifecta and more - but "Month" ain't it. It's a bridge too far, too pointless - it is the wrong abstraction.
And, yes, you can math on months. For indexing into arrays of localized strings, for conversion to/from epoch... I've written some really nasty .natvis visualizers that rendered absolute timestamps as PST timestamps for debug convenience, and it involved a whole lot of rather redundant and duplicated math in that horror of an XML format which would be made 10 times worse by extra "help" in the form of strong types to be unwrapped.
> I'd just hover over it in my IDE and it'd tell me what it is.
This works until some jerk buries it in #ifdef soup and causes your IDE to lie. Perhaps uint8_t - except on windows where it might be int for "backwards compatability". Or perhaps multiple definitions in different independent contexts (be that different namespaces or different included headers.) I've seen stupider shit frequently enough that the habit of spending more time performing a more thorough check than a simple hover will be time well spent.
> you can pretty easily fix all of the users of a "Month" type
Nah, that kind of underbaked leaky misabstraction isn't going to be properly thought out well enough to simply fix by merely poking at the type (and I'm confident in assuming it will be an underbaked leaky misabstraction on account of the lack of value provided.) You're going to have to actually audit all use of that type for dumb abstraction-breaking assumptions that make it brittle and fragile if you so much as sneeze on it's layout. A saving grace here: so little code will actually bother to use it, that it actually shouldn't be too terrible. Probably.
> Is "Month" a 1-based month ordinal? A 0-based month index?
Unlike with an bare int, a "Month" type has places to actually document all that. So your counter argument, which is entirely the same for an int anyway, is utterly absurd.
> This works until some jerk buries it in #ifdef soup and causes your IDE to lie.
I don't really know what your point even is here. Your ide or code base is bad, therefore everyone should share your pain?
If your ide can't resolve it then go look up the docs the old fashioned way. It's the same end result of a wrapper type being vastly superior to a bare int. I can make a comment in a header or a man page or whatever for a "Month" type to answer all your questions. I could never do the same for an "int" variable.
It really sounds like you're just wanting to obsessively nitpick the specific example of "Month" than anything about what the guideline is even saying. If you're just too lazy to make a Month then sure knock yourself out. But at least be honest that you're just being lazy in the now with a hope it doesn't bite you later, don't pretend you're making an actually better or more readable decision. We all take the lazy out from time to time.
> Unlike with an bare int, a "Month" type has places to actually document all that
Good chance it won't actually be documented there though, or that the documentation will lie. Similarly, that documentation can easily live on a date-style object, where it's more likely to be accurate.
> So your counter argument, which is entirely the same for an int anyway, is utterly absurd.
No, I've eliminated several possibilities with a plain int that you've conveniently left unquoted. I do still have some ambiguity, but less, and gained clarity in several things you've - again - conveniently left unquoted. It's not perfect, but it's an improvement.
> I don't really know what your point even is here. Your ide or code base is bad, therefore everyone should share your pain?
Every codebase has some bad code, and my point is - even with good IDE assistence - a typedef still requires digging. And if my own personal experience is anything to go by, most codebases have a lot of bad code.
If you've been blessed with only perfect codebases handed down to you from the gods themselves, pristine and pure, maybe Month is less opaque for you than it is to me. You have my sympathy for the shock and horror you'll experience should you ever touch code writtten by mere mortals ;)
> It's the same end result of a wrapper type being vastly superior to a bare int.
I challenge you to share a single real life bug that you've managed to catch or prevent with a strong month type. Go on, I'll wait. Or show me some code from a real codebase where a stronger month type actually improves readability more than, say, a variable named month_index or month_no. Show me a concrete example of this vast superiority improving the readability of a function in a real codebase. Or where it's lack has made things confusing, and the mere addition of "Month" would provide clarity.
> I could never do the same for an "int" variable.
You can absolutely attach comments and documentation to variables, methods, and properties.
Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property? Bonus points!
> It really sounds like you're just wanting to obsessively nitpick the specific example of "Month" than anything about what the guideline is even saying.
I'm providing context to point out the limits of the guideline and where it goes too far, using it's own example. Hardly obsessive nitpicking.
> If you're just too lazy to make a Month then sure knock yourself out. But at least be honest that you're just being lazy in the now with a hope it doesn't bite you later, don't pretend you're making an actually better or more readable decision. We all take the lazy out from time to time.
Don't get me wrong, I'm incredibly lazy. Otherwise known as cost efficient. I have better things to do than to create or untangle a mess of opaque types that do little more than obfuscate the code for some hypothetical type safety that - based on personal experience - won't actually add any safety in practice, won't catch any bugs, won't provide a meaningful improvement to documentation or code readability, and will just slow me down and take time away from working on something that might actually be important (perhaps some practical type safety.)
I would hope my coworkers also have better things to do with their time as well. The possibilities and backlog are infinite, but our time is not.
> Good chance it won't actually be documented there though, or that the documentation will lie
The most natural fit for the Month type here is an enum, enum class or something that wraps one with additional functionality. And while sure you could have enum Month { }; and then use Month(0) for january or whatever, that's just deliberate obfuscation and not what anyone reasonable would do for normal code. And it's still not worse than an int.
> No, I've eliminated several possibilities with a plain int that you've conveniently left unquoted.
Do you? Someone could have added a #define int float after all. Your arguments that just because someone could obfuscate the type in some way that that inherently makes using a Month type bad is just that: absurd.
> Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property? Bonus points!
Except if you are assingning one month property from another month property you have to check the documentation for both that they math (and not just almost match). Whereas with a strong type the compiler checks that for you.
> The most natural fit for the Month type here is an enum, enum class or something that wraps one with additional functionality.
And I remain unconvinced that any of those "natural fits" add value.
> Do you?
Yes.
> Someone could have added a #define int float after all.
That would be straight up undefined behavior. Can't redefine keywords. Even a badly defined "i32" is clearly buggy enough that even the most foolish coworkers won't do it, and cleaning up after the outright malicious is at least more straightforward.
You're trying to say that the likelyhood of stupid tech debt from weird backwards compatability nonsense, and straight up intentional chaos, but I don't buy that you even believe it yourself. I have seen plenty of the former in real codebases, and a tiny fraction of the latter. Even the broken-ass codebases that I've worked on that have invoked undefined behavior by redefining keywords haven't gone as dumb as #define int float.
I've seen #define true 1 though.
> Except if you are assingning one month property from another month property you have to check the documentation for both that they math (and not just almost match). Whereas with a strong type the compiler checks that for you.
Even if you have two type-matched "Month" properties, if they belong to dates in different timezones, there's a good chance you've just written a bug by forgetting to account for the difference - and that's a far more likely bug to slip through the cracks than mixing up month index and month ordinal. And when you don't have type-matched "Month" properties, the author will likely write the same code they would've with integers, just with a bunch more casts, if only because they don't want to eat the recompiles of touching a date/time header that gets included goodness knows how far and wide.
Putting it another way: I'd argue that the manipulation of individual months is already inherently not type safe, as it lacks enough context to be type safe.
> You can absolutely attach comments and documentation to variables, methods, and properties.
So instead of documenting it in a single place, your argument is to document it in dozens or hundreds of places and somehow that'll never get out of date. Even though the single documentation in a single place must be assumed out of date?
Cool story bro.
> Oh gee, is that an `int` month property that states it's range? Yes. Yes it is. And I don't have to drill further into the documentation to look at the type of the property, and can just look at the property?
How do you know what is January? Is it 0? 1? Or something else entirely because someone thought it'd be cute to use the value of "JAN" since, after all, that 4 byte string perfectly fits in a 4 byte int.
> I have better things to do than to create or untangle a mess of opaque types that do little more than obfuscate the code
enum class Month : int {
January,
February,
...
}
Holy shit look at that obscene effort and obscurity! I should enter IOCC with that unreadable masterpiece.
> won't actually add any safety in practice
`new Date(5, 10, 2020)`
Quick, what's the order of those arguments? Is it day month year? Or month day year like Americans like to do?
> So instead of documenting it in a single place, your argument is to document it in dozens or hundreds of places and somehow that'll never get out of date. Even though the single documentation in a single place must be assumed out of date?
When the documentation lives alongside the actual concrete use cases, it at least tends to get fixed when those individual concrete use cases are changed.
> How do you know what is January? Is it 0? 1? Or something else entirely because someone thought it'd be cute to use the value of "JAN" since, after all, that 4 byte string perfectly fits in a 4 byte int.
You didn't read the documentation I linked I see. "The month component, expressed as a value between 1 and 12." - this rules out fourcc nonsense, this rules out 0, this leaves only 1.
> Holy shit look at that obscene effort and obscurity!
0-based? Nice curveball number #1. Someone will probably refactor it to make it 1-based later, so that's nice curveball number #2 that will break all to/from int casts for interop. Or fix them - lets be honest, someone's going to cast (Month)some_var_that_is_1 for interop and expect January, and then probably cast back to (int)month for more interop, resuling in bugs canceling out bugs. I've also seen similar enums get accidentally reordered by someone fat-fingering Alt when pressing up-arrow, swapping lines, changing the values, for curveball #3... hope it gets caught in code review! It won't if such a typo sneaks into the initial version, but one can dream! So now I gotta read more than you could bother to type out...
> Quick, what's the order of those arguments?
Year month day. Easily verified by intellisense, too, since we're clearly okay with that in-thread. Your code will throw ArgumentOutOfRangeException. Someone will add these kinds of overloads for "convenience" even if you have a Month enum too BTW, so don't pretend that it's existence fixes things. I can totally get behind named arguments:
new Date(year: 2020, month: 10, day: 5)
Or "named" constructors:
Date.FromYearMonthDay(2020, 10, 5)
Either of which I'd prefer over:
new Date(new Year(2020), Month.October, new Day(5))
Programmer style YYYYMMDD isn't that bad either, honestly. But, even with that last one: What the heck is the timezone? UTC? Local timezone of the executing machine? Timezone of whatever machine is interpreting the Date instance? I'm gonna have to read the docs regardless...
> 0-based? Nice curveball number #1. Someone will probably refactor it to make it 1-based later, so that's nice curveball number #2 that will break all to/from int casts for interop. Or fix them - lets be honest, someone's going to cast (Month)some_var_that_is_1 for interop and expect January, and then probably cast back to (int)month for more interop, resuling in bugs canceling out bugs. I've also seen similar enums get accidentally reordered by someone fat-fingering Alt when pressing up-arrow, swapping lines, changing the values, for curveball #3... hope it gets caught in code review! It won't if such a typo sneaks into the initial version, but one can dream! So now I gotta read more than you could bother to type out...
This whole made up scenario equally applies to ints but even worse. At least with the type I can find the usages. The Month type is at worst not better than the int type, but it's never worse which is the important part of being a guideline.
Your argument is basically boiling down to "the safer code is more error prone because I'll just suddenly be a way worse programmer for some reason whereas I can totally nail the dangerous code without any possibility of mistakes ever because... uh... just trust me or something"
> Year month day.
Says who? Remember documentation doesn't exist in your hypothetical argument world, which is why a type is somehow bad or something.
> Easily verified by intellisense, too
This is what intellisense will show me: `Date::Date(int, int, int)` That's not verifying fuck all. If we were using opaque types then intellisense would verify it for me.
> I can totally get behind named arguments:
Sure, is that your guideline? We don't need opaque types because we only ever used named arguments and return values don't exist?
You really think having a type tells you less than having an int? Is the int 0 or 1 based? You don't know. Does the int handle rollover? Nope, you have to. Of course, that's the C way. Worse is better. Fuck sensible type systems and use of them. Let the world burn.
You can look up types, with a bare int you have to pray someone documented it and that they were consistent.
> Give me stronger types for dates or timespans or instants though.
So the guideline you're vigorously arguing against you don't actually disagree with at all, you just really, really hate Month for seemingly no reason.
Maybe give the guideline a read rather than just the 2 line snippet someone else posted?
> "Month" won't handle rollover properly on it's own either.
It does by not offering math operators in the first place, avoiding the entire possibility of a rollover from the outset.
Or it could throw on rollover so it's at least an runtime exception instead of a silent failure.
It doesn't need to be about OOP and hiding details, this seems like basic type safety. Both of these are member functions and are hiding details already, so OOP is an aside, you would need invest effort reading on both or neither if there are robust unit test that solid OOP would enable.
With ints as months is 0 or 1 January? What about -1 and 13, do they have sensible meaning? You need to re-apply that effort constantly to any int that might be a month. This isn't a hidden implementation this is an easy mistake waiting to happen. Using a month enum solves this, and you are not fixing bugs in the implementation of an enum unless you are a compiler developer.
Same with const, it prevents accidental changes to the class from this member function. It allows more sensible code and compiler optimizations. The "do" line will almost always be faster than the "don't" line. There is no way adding const changes the amount of effort for a reader in a negative way.
Please stop sharing my secrets. I've done pretty well fixing up things with basic type safety because my predecessors couldn't be bothered to use their brains. I like being employed.
> It allows more sensible code and compiler optimizations.
Unfortunately no, const only tells the compiler optimizer something when applied to variables but not for reference or pointer types (or member functions, where it just means apply const to the this pointer). It would be nice if there were a stronger co_const that would tell the compiler could assume is never cast away (or at least that the variable is not modified through that pointer/reference even if the const is cast away) but C++ const is not that.
The const in this example is on the member function so nothing external can cast it away, but the value of it there is really more about contract/behavioral enforcement than optimizations yes.
> Multiply this experience by the number of all the layers in the code and maybe others might be able to appreciate why I prefer the second type of coding. Please someone tell me I am not the only one who feels this way!
I don't understand the downvotes. You're expressing your opinion nothing else.
That said, I generally agree with you. Layers of abstraction have a cognitive cost. That cost might be paid for by gains due to those abstractions, but the costs exist nonetheless. Whether this is bothersome is up to the programmer in question, but it is certainly a legitimate concern.
> Back in 2010, I read the c++faq religiously, tried to warp my projects to it in college, and in the end produced nothing because i was too busy trying to be a big boy.
You're just admitting to having created your own problem, and suffering consequences for it. Nothing in your comment pertains any issue with reading coding guidelines in general or specifically the C++ ones, let alone any comment in particular.
> Now, the c++faq is obsolete.
That's your personal assertion, and one that does not sound informed at all.
I keep track of the C++ coding guidelines. In general they are nice rules of thumb. No one wants to build a religion around them.
Cargo cult programming has zero to do with religion or what features ship and do not ship with a standard library. Please read the wiki page you're trying to quote before quoting it.
you don't necessarily have to read it. Many of the guidelines are implemented as checks in clang, for me the ones I find relevant and enabled show up inline when I write code in my IDE.
I love this document, because I think that it's a perfect microcosm representing all the problems with the CPP style of doing things are.
The guidelines document is:
- Self admittedly incomplete
- Self admittedly outdated
- Extremely long
- Not targeted at developers first, but at analysis tool writers [1]
- Raises the question (to me, at least): Why doesn't this document exist since the nineties? What was going on there? What took so long?
And you can use this set of bullet points to basically describe everything that CPP does and all its complexity. Compare it to Python's pep8 and it's shameful, even. They tried to write some guidelines and the language was so big and bloated that they couldn't complete the work, plus the result is so inaccessible that most developers will simply ignore it completely.
I work professionally with CPP right now and all these issues are constant sources of pain to me. I want to like the language, but everything is always like this.
[1] "We do not expect you to memorize all the rules before trying to write code. One way of thinking about these guidelines is as a specification for tools that happens to be readable by humans."
Do guidelines that claim to be complete and perfect actually reach that level? They preface this to cut off arguments were someone tries to slavishly follow rules even when inappropriate, something that is all too common in our industry.
It is long because they work on adding examples to each one and specify precisely what they mean. Striving to reach the level of precision a pedantic C++ developer might demand when ambiguity arises.
This document didn't exist in the 90s because the way we shared information was fundamentally different back then, the web sucked. Also, many of these relate to more recently learned lessons or specific things about C++11 and it would be odd to mandate time travel for one document.
Of course, I didn't mean this specific document, just a guidelines document similar to this, but for older versions of the language. I'm sorry for comparing to Python again, but it's a much younger language and it has had an official document equivalent to this one since 2001. Remember, the Core Guidelines project was announced in 2015, after C++14 was already in production systems all over the world.
C++11 has existed for more than a decade, it has already been superseded by 3 newer versions of the language that add a lot of new features (some of them incompatible with each other), yet for most of its existence there was no official guideline from the creators that explains how it should (and shouldn't) be used. And as far as I know, nothing like it existed for C++98. It's too little, too late. They clearly have some priorities regarding documentation that, as a user, I strongly disagree with.
Big organizations (EA, Unreal, Google, etc) can live with this -- they can afford to have their own, very restrictive standards on how to use the language. Most small organizations (in my experience) don't, either through lack of expertise, money, time, or will (or a combination of all), and their code is usually ugly, hacky, unstable, and hard to work on. This document only solves this problem insofar it exists as a very strong recommendation to buy ReSharper and just do whatever it says.
Yeah, there probably should have been more efforts to get something online early. But consider that the Bjarne, the original creator of the language, has published several books, that is where a lot of the guidance is from. Some of the books were out in the 90s and others wrote books too, I have one from Angelika Langer about IOStreams that is nearly 20 years old now, Scott Meyer a Microsoft compiler dev publishes stuff, Chandler Carruth a Google compiler dev publishes in depth talks. There is a lot of C++ material, just not much from a central source and not compiled into a single list. This makes it easier to go from a novice to an expert, and harder to become a novice in the first place
Before I got to your last paragraph, I was like "but big companies manage it fine" and you are right that it isn't readily approachable for non-expert small teams. There are things like Jason Turner's starter repos for C++ but he makes assumptions and even he went the book route, and is non-official.
I think a lot of this comes down to age and size. C++ is astronomically large. I don't want these to be excuses, you raise legitimate questions that need to be addressed, but I do think every language will deal with this when getting this large.
C++ just formed differently than a lot of languages. Most languages have 1 canonical source or at least had 1 source before the modern era of package managers and docs bundled with the language. C++ existed a good 25 years before package managers for languages became popular, so even getting libraries is hard. But it does mean that there are many package managing system for C++. Many sources of docs, and many implementations. You have freedom to pick your own at the cost of needing to know how to make such choice.
Compare to Ruby; Yukihiro Mastumoto (Matz) made a Ruby interpreter, CRuby the community sometimes calls it. Then someone remade it on the JVM, JRuby. Then they, some nebulous they that were invested in Ruby, made a standard for the core language. Then Twitter, Rubinius, some C# dudes, all made their own, but the CRuby implementation was the reference so deviations were bugs. The stand exists, but at 1500 pages (language and core library) or something like that the language is underspecified so everyone looks to Matz for answers, and maybe the Spec is updated with his blessing. At some point he blessed RubyGems as the de facto package manager and even though other exist they are generally ignored.
In C++ everyone follows the standard, even Bjarne Stroustrop the creator doesn't have his own compiler anymore. When there are deviations from the Standard everyone knows the standard is official and can read up enough from a variety of public sources. There is no single source of truth like Matz for Ruby, but there is no bottleneck on technical details either. The C++ standard is also much more detailed, coming in at two 3k page docs, 1 for the language and another for std lib even though both are much smaller than Ruby. Because there have been so many technical arguments there is much greater specificity and thought put into details. All of this openness comes with vagaries and difficulty getting into it.
C++ isn't a turnkey solution for a specific domain, it is a very general purpose tool for experts. Nothing is stopping someone from packaging as a turnkey solution and some the big companies you mentioned do exactly that for their internal development.
Pro tip: clang-tidy understands pretty much all of the cpp core guidelines, and if you're using clangd it can emit clang-tidy warnings for violations (and in some cases even provide fixits). Not to mention a bunch of other really useful style rules. You should definitely be using it if you're not already.
Kind of, if you happen to have the right include paths and compiler flags, which even a compile command database from e.g. CMake doesn't give you (by default)
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines