Repository navigation
fix: let the accessors be called on a temporary - #86
Merged
Merged
Conversation
The accessors take their object by deducing this, declared as an lvalue
reference, which cannot bind to a temporary. So a result could not be read from
the expression that produced it:
(a * b).f() error: candidate function not viable:
sqrt(a).g(0) expects an lvalue for object argument
a.sin().h(0, 0)
Every C++ caller had to introduce a named variable for every intermediate
result. Six accessors were affected: data, ptr, f, g, h(i) and h(i, j). The
object parameter becomes a forwarding reference, which binds to lvalues, const
lvalues and temporaries alike.
As with std::vector::operator[], a reference obtained from a temporary is only
valid within the full expression, and no compiler diagnoses keeping it. That is
noted where the accessors are declared.
The writable path is unchanged: result.f() = value on an lvalue still works,
which the factories rely on, and so does reading through a const lvalue.
Tests state the property twice over. Concepts check that each accessor is
callable on an rvalue, across all four combinations of order and sizing -- 18
assertions, all of which failed before the change. Then the values read from a
temporary are compared against the expectations the surrounding cases already
use, including a chained read, a.sin().exp().f(), which is the case that makes
this worth fixing.
C++ 106 test cases, 1362 assertions, Debug and Release with -Werror and
under clang with ASan+UBSan
Python 239 passed
clang-tidy unchanged at 50, clang-format and ruff format clean
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The accessors take their object by deducing
this, declared as an lvalue reference — which cannot bind to a temporary. So a result could not be read from the expression that produced it:Every C++ caller had to introduce a named variable for every intermediate result. Six accessors were affected:
data,ptr,f,g,h(i)andh(i, j).This came in with the deducing-
thisrefactor of the C++23 modernisation, which replaced the duplicated const/non-const overload pairs. The old pairs had a const overload that bound to temporaries; the single deducing-thisversion does not.Fix
The object parameter becomes a forwarding reference —
this auto &&selfinstead ofthis auto &self— which binds to lvalues, const lvalues and temporaries alike. One character per accessor.SScalaris unaffected: itsf()andd()return by value.Caveat, and it is documented in the header
As with
std::vector::operator[], a reference obtained from a temporary is only valid within the full expression:I checked:
-Wall -Wextra -Wpedanticemits nothing for that. The note sits where the accessors are declared.What did not change
The writable path:
result.f() = valueon an lvalue still works — the factories rely on it — and so does reading through a const lvalue. Verified separately as well as by the suite.Tests
The property is stated twice over.
Callability, as concepts over all four combinations of order and sizing — 18 assertions, all of which failed before the change:
Deliberately expressed as concepts rather than as direct calls, so the red state is a failing assertion rather than a build error.
Values, read from temporaries and compared against the expectations the surrounding cases already use — including a chained read,
a.sin().exp().f(), which is the case that makes this worth fixing at all.Verification
-Werror, and under clang with ASan+UBSanclang-format --Werrorandruff format --checkcleanNotes
Independent of #84 and #85 — this touches
DDScalar's accessors, those two touchSScalar. Based onmain, merges in any order.Found while measuring for the
SScalarwork: a benchmark of mine would not compile, and the compiler error turned out to be a defect in the library rather than in the benchmark.