Search code examples
c++c++20std-rangesstdoptional

Pass `std::optional::value` to `std::views::transform`


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();});

Solution

  • 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.