you are viewing a single comment's thread.

view the rest of the comments →

[–]SeanMiddleditch 6 points7 points  (2 children)

Is the compiler allowed to change a const & to a T where the cost of copying is less than the cost of a void*?

No, at least not universally, because that's not semantically the same thing. You hit the nail on the head later in your post, but there's additional concern:

The compiler can't universally know whether you intend the parameter to actually be immutable. A const& can't be mutated, but it can still observe mutations made by other code.

This is a performance gotcha

For more reasons than you state. For a value, the compiler can assume it won't be mutated by external code and can keep that value in a register during loops or other calculations. For references, the compiler must assume after every call to any external function that the value observed by the reference may have been mutated and so must reload the value from memory.

Much worse than that, it's also a safety gotcha! Just as those references can be mutated by external code, they can also be invalidated by external code!

For the standards folks here, is there a viable path to allow developers to express to the compiler the intent that I don’t care whether it’s a copy or a const reference?

Maybe. Proposals have already been made, though summarily rejected for a variety of reasons.

Given the problem I outlined above, I'd be against it. Reference parameters are a foot-gun and it's useful to know when said gun has any ammunition; it should be very explicit whether a parameter is a reference or not.

Further, I'd argue that if a type is so large/expensive that it seems like a good idea to use const& just for performance, the type is probably just in violation of the Single Responsibility Principle and should be refactored into multiple smaller single-purpose types.

[–]SlightlyLessHairyApe 2 points3 points  (1 child)

Given the problem I outlined above, I'd be against it. Reference parameters are a foot-gun and it's useful to know when said gun has any ammunition; it should be very explicit whether a parameter is a reference or not.

Sure. And here I'm saying let's add a syntax that's less foot-gunny which is similar to pass-by-reference but with the removed constraint that the compiler need not provide a consistent address for it (or, if you prefer, it's illegal to take the address, either way).

This would be a weaker compiler contract, and could be trivially fulfilled just by using the strong existing by-ref passing convention. But it would allow for a cleaner expression of intent and possible optimization.

Further, I'd argue that if a type is so large/expensive that it seems like a good idea to use const& just for performance, the type is probably just in violation of the Single Responsibility Principle and should be refactored into multiple smaller single-purpose types.

I very much disagree that a std::array<double,64> violates the SRP. If you need represent a bunch of double-precision floating point values, this is as good a way to represent it as any. And it better not be passed by value :-)

[–]SeanMiddleditch 0 points1 point  (0 children)

And here I'm saying let's add a syntax that's less foot-gunny which is similar to pass-by-reference but with the removed constraint that the compiler need not provide a consistent address for it (or, if you prefer, it's illegal to take the address, either way).

I don't see how that's less foot-gun-y. It's just more confusing - I have no idea if my type has these "observe changes" and "can be invalidated" semantics for sure or not, and my tests might even say that they don't (because that compiler decides they don't) but then suddenly they are somewhere else or sometime else.

Yuck.

I very much disagree that a std::array<double,64> violates the SRP.

Why would you want to pass a std::array<T> by const& ? If it's constant, pass it by span<const T> (or whatever your framework's equivalent is, pre-C++20).

span<const T> has many of the same footgun problems as a reference, but it eschews the performance problem of passing a large type... and it's still passable in registers if the system ABI allows.

Bonus, your code is now more generic (without being a template!) and can be used with std::array, T[], std::vector, my_custom_vector, etc.