Issue 172197
Summary `std::optional<T>::transform` does not support user specializations
Labels new issue
Assignees
Reporter howtonotwin
    If a user defines their own specialization of `std::optional<P>` for some `P`, then libc++'s `std::optional<T>::transform` (for some other type `T`) will not support `transform` into the new `std::optional<P>`, even if `std::optional<P>` satisfies all the requirements imposed on it by the standard.

This is because libc++'s definition for `std::optional<T>` assumes that every other `std::optional<U>` is also a specialization of the libc++ definition. Specifically, libc++'s definition for `std::optional<T>` exposes a nonstandard "private" constructor (gated with a tag whose name is a reserved identifier) https://github.com/llvm/llvm-project/blob/7db97696a22517a18734285e712779fd8d426653/libcxx/include/optional#L925-L926 and, in `transform`, expects a similar constructor to be present on the different specialization `std::optional<U>` https://github.com/llvm/llvm-project/blob/7db97696a22517a18734285e712779fd8d426653/libcxx/include/optional#L1144 libc++'s `std::optional<T>` should instead be programming to the *standard* interface of `std::optional<U>`.

I *believe* it is possible to implement `transform` correctly without assuming that the target `std::optional` specialization has any more constructors than specified in the standard (see my implementation of `transform` below). If that is the case, then libc++'s non-support of user-defined `std::optional` specializations is a bug. (If it's *not* actually possible for user-defined specializations of `std::optional` to be correct, then I suppose libc++ is in the clear for not supporting them, but then I think we'd have a standard defect...)

<details><summary>Example</summary>

```cpp
#include <optional>

class my_bool { // a program-defined type I might want to specialize std::optional for
  char x;
public:
  explicit my_bool(bool x) : x(x) { }
  my_bool(my_bool&&) = delete;
  ~my_bool() { };

  explicit operator bool() const { return x; }
  my_bool operator!() const { return my_bool(!bool(x)); }
};

template<> class std::optional<my_bool> {
  struct absent_t { char sentinel = 2; };
  union { my_bool present; absent_t absent; }; // saved a byte 🥳!
public:
  // relevant members
  constexpr optional() noexcept : absent() { }
  constexpr optional(nullopt_t) noexcept : optional() { }
  optional(optional const&) = delete;

  template<class ...T> requires is_constructible_v<my_bool, T...>
  constexpr explicit optional(in_place_t, T &&...t) : present(std::forward<T>(t)...) { }

  template<class L, class ...T> requires is_constructible_v<my_bool, initializer_list<L>&, T...>
  constexpr explicit optional(in_place_t, initializer_list<L> l, T &&...t) : present(l, std::forward<T>(t)...) { }
  // converting constructors and other gory details omitted

  constexpr bool has_value() const noexcept {
    return absent.sentinel != absent_t().sentinel;
  }
  constexpr ~optional() { if(has_value()) present.~my_bool(); }

  template<typename F> constexpr auto transform(F &&f) &;
};

// how *I* would implement transform (other overloads similar)
template<typename F> struct later {
  F f;
  operator decltype(auto)() && { return std::move(f)(); }
};
template<typename F> constexpr auto std::optional<my_bool>::transform(F &&f) & {
  using U = remove_cvref_t<invoke_result_t<F, my_bool&>>;
  if(has_value()) {
    return std::optional<U>(in_place, later([&] -> U {
      return std::invoke(std::forward<F>(f), present);
    }));
  } else return std::optional<U>();
}
```

Now my `std::optional<my_bool>` can `transform` to any libc++ `std::optional<T>`, as well as to itself, but libc++ `std::optional<T>`s don't know how to construct my `std::optional<my_bool>`:

```cpp
#include <format>
#include <iostream>
#include <string>

int main() {
  std::optional<my_bool> mb(std::in_place, true);
  // okay: mine -> mine
  std::optional<my_bool> mb2 = mb.transform(&my_bool::operator!);

  // okay: mine -> libc++
  std::optional<std::string> fmt = mb.transform([](auto const &mb) { return std::format("{}\n", bool(mb)); });
  if(fmt) std::cout << *fmt; else std::cout << "absent\n";

  // okay: mine -> libc++
  std::optional<bool> ob = mb.transform(&my_bool::operator bool);
  // ACK! libc++ -> mine
  std::optional<my_bool> mb3 = ob.transform([](bool b) { return my_bool(b); });
}
```

On [Godbolt]
</details>

[Godbolt]: https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAMzwBtMA7AQwFtMQByARg9KtQYEAysib0QXACx8BBAKoBnTAAUAHpwAMvAFYgATKVpMGoZAFI9AIQuXSS%2BsgJ4BlRugDCqWgFcWDCaRuADJ4DJgAcr4ARpjEIACs8aQADqgKhM4Mnj5%2BAanpTgIhYZEsMXGJdpgOhQxCBEzEBNm%2B/lxVNZn1jQTFEdGxCUkKDU0tue0jPX2l5UMAlHao3sTI7BwWAMyhyD5YANRmm%2B78xCxMBEfYZhoAgls7e5iHx84jxJisVzf3etsMu28ByO7neoWA3zuPweAKeL08yVqYkh9zuuyYCgU%2BxYAE8APpRVBeQ4Adks%2BwA9BT9kx9sliKhgMRWABaLA0MLofYEHHJZ4ASWxeGACAI%2BwA7sYxUR9go%2Bcg8GI8AAvZ4jdAgECoRGZMT7U4/fb7ZAIRr7VRHax3ZLeKK0PDIECG/aYVTJe0KsW4glE2gQQnE1TzfYgc0QIOk8lmEkAEWd3oDfoTvosADY08GjjH9lh6ARMJbnQA/ZNeCCZsmkuObK2o25G13uh2Efba2IXEj7RPl40CEaR/YfAgrBjmy1V%2BP4xOtvnMojEMCQYNoBj96Pkocj7FT30QRfdoPzcfRuNQ2OFqF3fMsd0XAvHK7GoyY2UEDVanUCZHHUu0R/r513m8RwaSiJRBDxMV12NU1iFlRgnDCWgXmzPRj3PGtnW8BhMgHX86Q%2BcDLhrUCiMg0iEPQ6tySpWUmAAN0wLlaSiHF80OAAxDQzAATg4sxbniMxLE2RcfhtO0HSdO4jVoj56HoqVsUwMpYgUZ0VxGRs4O1JE/WDBhUFdNZERDCjBB7aCTw0vt8zdHTP2YP0GG8WhaF0yCDKM1QTLFUNdN1fSB2smSZz0iAAq/ZDNMuPRU0zTZs1zTB8wvOsjWvW9UuOdEXwAOgKgAVR8PgAR28PBCP2PAFDxGLiGApw7UwPF6JBX9SH2QqCrylEjRi7SXTdD0W0ipyIFCPFbzWSDOsKw44rTHqCGDUN6UwIiIHVTVTklYh0BBYrNmwCAVp6iso3PS9625FSsvvdxcqxIJOqe/YeqO7BB0wcrKo26ravqxq8Ga1r2p3LxOtCDIlVVYg8XtEYQSCb44rmnq%2Bt7Vc7PpIam09MLAomhgpqMGaCChnCnFh2IEZq4j3BR479loOaFvTOLltWgiNoQiBWdfd9dsaA7jk%2B075nO4Krpu2iV0YppwSxoDHBILFjC5VACAQWJ9mAEgcRzFKmDoLFUBYQh8wO67%2Btswbp1NWrFJ8TAexi/ZDOMzBTIA0KjU3YhRyYMCELyojQmqfYxMS8yCEg8sw4QiO/0w0KQpugb7P2IsxrESzKzwKgIEd1qxG8V35mDdaiLyksIf0qjoVCzKjGy9weT5Zg2H2fjmcz3GmG8GUCGZVdThYCAOPZtMqEzRbU/uDDax%2BWiEFQcV9gAKn5TeJWWWguTwG96DYQRuVHhRx/2CLtd11AFfcph0CxdILaMYgjyvO7W4ejvGFYZ4vcvoqzFD/OCvsbpTyoGlI0bY5ydiwLsP%2BEBB5EHmJZRacUBwB1HNtEALB76u1nuWRuZ5qI/BbneEEf8u6AMfP3OCqDUCC01LnFO7hfxXE1CPYwl8SATynmmGec9UyRiwukEw%2Bw5AoW%2BgQxidV6IfCoJBEEoR6KoAANYtUIq5OODMOKdU4fPa4x0YHVSLiXZ25dywXWdP7FKW48FsJBHIK4xNSZMDWJ1MBEAzDxGsIteI2YWSPmkRAo0ETvrDkDiwkAajNGuzwcLfaIIgHUEWDzIiR4F6RJPJXMxJ4XS0CUFExxb5WGOW/O4Vxx0SEL3Tj8UIXoTYMHzrWI0TjKnsM4czMoW1ylxJJtNTAnUR7l2ye02Jzifz10fGUPQMiyh5R4WPfhvi4q/gqe2eci4JlNxup0vSII8FghMI%2BKgLAoIxyWSsvhZxfH%2BL8TGFBQ9mHuzTGUC6pSYlJP4RcdZaEyTWXiO4BgFgDBdl3J8iZVY9mhULtQS5wY8FoCHvCEEW8LnEXJNUEpKLlhXPcBi8FwciJ%2BNBeCtKgEBnTPcImR8qAoiLKiMsi%2B491mpk2R%2BbZnZExwtltSW47gADSi5r72iiOYKwNgpldMOlwkAtyr41W5DrVsVAqBuFiJ/A5NK5UzJ9F4OZURNgyMZay3h7K/HWCCf6X0XYvk4O3Iav0UQYV5KpbGDgixaCcHiLwfwHAtCkFQJwIl0qrCymWKsZ4Ww9C8AIJob1iwdZP0GOWUgGiQCpk2HlRIqY9AaAAByphJBoDQPFEhcCSL6jgkheAsAkOW0ggbg2ho4LwBQIANCkETUG71pA4CwCQK6TAyBXlkAoCg4gwAFDKGMNUIQa9xSBp4KQNAN46AXEyPOpCS716tt4Bu5IdBBjohMJBBqDANHrvNie%2BgxBwgAM4Eeu9p7iAAHkh77pXUmwIqgx23BnV2jt/6x31HwIG3g/BBAiDEOwKQMhBCKBUOoftpBdDtCMCYFANgbCGBBl2pcIbHIgZZOqLMUqAmWD0MgfY5G3wSqzBKqjNheCEOIMQPAWAiMZqvU4NghVfS8cWAoaNax9B2DfKEXdi7l2roTR8dgPbxTMmSJwHgPq/UBr/e27AAHx3znNCWlkqZJBPmMMAa%2BYzr3BggOG6jnVcCEE7HG%2BYCak2V0zSATYqY8qSEkJsEkPEuCbD0DxHiGgAtFprZwetLbdMvrsN23tnmtMcHjQl9D7aPP9q8wrdILhJBAA%3D%3D
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to