I cannot seem to pass std::optional::value
to std::views::transform
.
However, I can pass std::optional::has_value
without any issues:
#include <optional>
#include <ranges>
#include <vector>
int main () {
std::vector<std::optional<int>> myVec{{1,2,3}};
const auto result = myVec
| std::views::filter(&std::optional<int>::has_value)
| std::views::transform(&std::optional<int>::value);
return 0;
}
As an example, here is the error I get with x86-64 clang 16.0.0 and passing the -std=c++20
flag:
<source>:15:11: error: no matching function for call to object of type 'const _Transform'
| std::views::transform(&std::optional<int>::value);
^~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/ranges:891:2: note: candidate template ignored: substitution failure: deduced incomplete pack <(no value)> for template parameter '_Args'
operator()(_Args&&... __args) const
^
/opt/compiler-explorer/gcc-12.2.0/lib/gcc/x86_64-linux-gnu/12.2.0/../../../../include/c++/12.2.0/ranges:2057:2: note: candidate function template not viable: requires 2 arguments, but 1 was provided
operator() [[nodiscard]] (_Range&& __r, _Fp&& __f) const
^
1 error generated.
Compiler returned: 1
Do you know what is going on?
Natually, as a workaround, I can use a lambda, though that doesn't feel nearly as satisfactory:
const auto thisWorks = myVec
| std::views::filter(&std::optional<int>::has_value)
| std::views::transform([] (const auto& opt) {return opt.value();});
The problem becomes very clear when compiling with GCC, which yields the error:
error: no match for call to '(const std::ranges::views::_Transform) (<unresolved overloaded function type>)'
15 | | std::views::transform(&std::optional<int>::value);
| ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pay special attention to <unresolved overloaded function type>
.
&std::optional<int>::value
takes the address of an unresolved overloaded function type because this member function has overloads for const
, non-const
, lvalue references, and rvalue references.
In total, std::optional::value
has four overloads, and the compiler doesn't know which one to choose when you take its address.
You can suppress the error as follows:
// This works because we are manually resolving the type through an implicit
// conversion, similar to how overload resolution works when calling functions.
int& (std::optional<int>::*value_fun)() & = &std::optional<int>::value;
const auto result = myVec
| std::views::filter(&std::optional<int>::has_value)
| std::views::transform(value_fun);
See live example at Compiler Explorer.
However, while this technically compiles, it's not pretty, and value
is not an addressable function.
This means that taking its address has unspecified effect, and the program might even be ill-formed. See also: Can I take the address of a function defined in standard library?
You should use your workaround with lambdas for both filter
and transform
. It is the idiomatic and correct solution, even if it isn't as concise.