We’ve encountered a limitation of the C++ library that doesn’t seem readily fixable without modifying the library, in turn requiring a corresponding small fix to the C++ standard.
Does the following look right?
For example, the following
https://godbolt.org/z/f45bPd4sz
fails to compile due to an ambiguous conversion:
// Implicitly auto-converts to anything.
static
const
struct default_value_t {
template <class
T>
operator T()
const {
return T {}; }
} default_value {};
int main() {
std::optional<int>
a = default_value; // fails to compile
return
0;
}
GCC 14.2 output for example (similar on clang):
<source>: In function 'int main()':
<source>:13:28:
error: conversion from 'const default_value_t' to 'std::optional<int>' is ambiguous
13 | std::optional<int> a =
default_value;
|
^~~~~~~~~~~~~
<source>:7:5:
note: candidate: 'default_value_t::operator T() const [with T = std::optional<int>]'
7 |
operator T() const
|
^~~~~~~~
In file included from
<source>:1:
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/optional:745:9:
note: candidate: 'constexpr std::optional<_Tp>::optional(_Up&&) [with _Up = const default_value_t&; typename std::enable_if<__and_v<std::__not_<std::is_same<std::optional<_Tp>, typename std::remove_cv<typename std::remove_reference<_Up>::type>::type>
>, std::__not_<std::is_same<std::in_place_t, typename std::remove_cv<typename std::remove_reference<_Up>::type>::type> >, std::is_constructible<_Tp, _Up>, std::is_convertible<_Up, _Tp> >, bool>::type <anonymous> = true; _Tp = int]'
745 |
optional(_Up&& __t)
|
^~~~~~~~
The simplest fix appears to change this line of the standard:
from:
!is_convertible_v<U, T>
to :
!is_convertible_v<U, T> || is_convertible_v<U, optional<T>>
Doing the associated edit on clang/llvm and glibc implementations of std::optional appears to fix the issue.
As a quick way to run a test suite, a similar edit of the TartanLLama implementation of optional
https://github.com/TartanLlama/optional
also works, and passes its test suite. Specifically, the change there was this:
/// Constructs the stored value with `u`.
template <
class U = T,
- detail::enable_if_t<std::is_convertible<U &&, T>::value> * = nullptr,
+ detail::enable_if_t<std::is_convertible<U &&, T>::value &&
+ !std::is_convertible_v<U &&, optional<T>>> * = nullptr,
detail::enable_forward_value<T, U> * = nullptr>
constexpr optional(U &&u) : base(in_place, std::forward<U>(u)) {}
template <
class U = T,
- detail::enable_if_t<!std::is_convertible<U &&, T>::value> * = nullptr,
+ detail::enable_if_t<!std::is_convertible<U &&, T>::value ||
+ std::is_convertible_v<U &&, optional<T>>> * = nullptr,
detail::enable_forward_value<T, U> * = nullptr>
constexpr explicit optional(U &&u) : base(in_place, std::forward<U>(u)) {}
Is submitting a defect report appropriate? How might we proceed?
Best regards,
Marc
PS. A similar limitation may exist elsewhere in the standard. For example, std::unique_ptr appears to have the same issue (while std::shared_ptr does not) on versions of clang and gcc on-hand. This was not investigated.