Skip to content

Conversation

@jbcoe
Copy link
Owner

@jbcoe jbcoe commented Nov 3, 2025

Run the new test experiment with bazel test //exploration:reference_wrappers_test.

The experiment can be launched from a devcontainer to save installing dependencies. See the README for details.

References:

@codecov

This comment was marked as off-topic.

@jbcoe jbcoe requested a review from Copilot November 3, 2025 23:04
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds exploration tests to demonstrate the interaction between std::reference_wrapper and xyz::indirect, specifically testing swap and move semantics. These tests help clarify how reference wrappers continue to point to the original objects even after the owning indirect instances are swapped or moved.

  • Adds two new test cases exploring reference wrapper behavior with indirect
  • Updates Bazel build configuration to include the new test target

Reviewed Changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
exploration/reference_wrappers_test.cc Adds tests demonstrating reference wrapper behavior when indirect objects are swapped or moved
exploration/BUILD.bazel Adds Bazel build target for the new reference wrapper tests
MODULE.bazel.lock Bazel lock file automatically updated with new transitive digest

EXPECT_TRUE(b.valueless_after_move());
// b-ref refers to the value it referred to before the move.
EXPECT_EQ(br, 4); // observing the lvalue via operator int&().
// Perhaps this is surprising??
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, and this means that, to reference_wrapper<T>, the wide contract on an operator T& does not make it safer. One can argue that a hardened, narrow contract operator T& is safer. When having such an operator on indirect<T> itself, it just means "the step to reach an lvalue of the object of T when the storage is managed by indirect<T>" has a narrow contract.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. I'd agree that reference-wrapper is unsafe here, but that's expected. I'm not sure that I see the point you're making clearly yet.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So "reference_wrapper is non-owning and has no null or valueless state" -- not right; reference_wrapper does have a valueless state, it's just that code cannot observe this state.

Imagine this world: the int object x that holds 42 has a narrow contract on evaluation. The contract is violated if you "move out" the value 42 and then bind it to int& -- isn't that just an operator int&() on int? But wait, what if all I want to do is to assign a new value with x = 7? It won't a problem if that x is in fact indirect<int>, because that expression calls indirect<int>::operator=(int&&).

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So indirect<T>::operator T&() with a narrow contract is totally safe, extra-safe I'd say. It gets involved in the right contexts, and is not involved when not in the right context...

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure what I can do about this: "reference_wrapper does have a valueless state, it's just that code cannot observe this state."

In any case the issues we see seem to be issues with reference_wrapper, not indirect.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That sentence is not meant to point out any issue with indirect. I mean, the whole motivation for indirect<T>::operator T&() has always been "support incomplete type T as drop-in replacement for T". It was P3902R1 saying that "reference_wrapper can have that because [...], it would be a safety hole in indirect" and I was saying "it's quite the opposite, reference_wrapper's operator T&() has inevitable safety issues, but indirect<T>'s operator T&() is free of that and would add extra safety to T."

@jbcoe jbcoe closed this Nov 7, 2025
@jbcoe jbcoe deleted the dev-reference-wrappers-experiment branch November 7, 2025 03:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants