I've noticed that using a lower-level language, where abstractions have to be built explicitly, tends to cause one to rethink the problem and often come up with an even simpler and more efficient solution, by approaching it from another direction which using a higher-level language may not even allow.
An example of this I encountered several years ago was with several coworkers who were trying with utmost effort to optimise a piece of code which the profiler had indicated was taking a substantial amount of time --- and a lot of it consisted of memory allocation and copying. They tried lots of "classic" tricks like unrolling, inlining, even reorganising the layout of several classes in an attempt to be more cache-friendly. I looked at the algorithm and realised rather quickly that the code in question was not necessary at all; some trivial modifications to code elsewhere which was using it and deleting that code completely resulted in 30x faster performance and 1/10 memory usage. Due to their background, my coworkers were stuck in the mindset that it was necessary to perform all that convoluted processing, and neglected to see the bigger picture.
So if what's written here is true, I may be unwittingly baking bad practices into my C++ knowledge as a direct result of trying to accomplish the exact opposite…
Which leads me to the question: what is the "right way"? I've seen highly vocal critics of writing C++ as "C with extras", so I assume some middleground is where I need to target?
Generally there's a lot of emphasis on RAII, clear ownership semantics, leveraging more of the standard library as its grown, using lambdas, avoiding shared mutable state, judicious but not excessive use of inheritance and in particular avoiding implementation inheritane, encapsulation.
It's not so much a middle ground in the sense you are thinking. The people I'm talking about don't advocate developers, particularly non expert, going crazy with templates. People do that on their own.
Hope that helps.
The one and only thing that muddies this picture is other people. Once you are collaborating on a program(whether on the same team, through end-user code, or through a library or API call) all your tricks, preferences and conventions are subject to other people's inept groping and misunderstanding. And that is where you get into standardized best practices. They are basically guaranteed to not actually be the best practice, but they're the one you can compromise on.
Citation needed? Better developers are more aware, in any language. There are some cases where idiomatic C++ may introduce more indirection over C (though I can't think of any); there are plenty where idiomatic C introduces more indirection than C++. However, with a little more effort and awareness, the faster and more maintainable solution is always accessible.
> I've noticed that using a lower-level language, where abstractions have to be built explicitly, tends to cause one to rethink the problem and often come up with an even simpler and more efficient solution, by approaching it from another direction which using a higher-level language may not even allow.
Having lots of developers, re-implement many things, mostly just results in much more buggy code. Getting things exactly right is hard. Having a bigger standard library and safer abstractions is a huge edge.
I don't think your anecdote has anything to do with C vs C++. I think basically some negative experiences with so-so C++ devs has colored your thinking rather than technical reasons.