Search code examples
c++c++20std-rangesc++-modules

Invalid operands to binary expression when importing custom view from a module


I've defined a custom view:

namespace detail {

constexpr auto coord = [](auto&& p) -> decltype(auto) {
    return p.coord();
};

struct CoordsView
{
    constexpr CoordsView() = default;

    template<std::ranges::range R>
    friend constexpr auto operator|(R&& r, CoordsView)
    {
        return std::forward<R>(r) | std::views::transform(coord);
    }
};

} // namespace detail

inline constexpr detail::CoordsView coords;

and I am using it in the following way:

struct Point
{
    float x, y, z;
};

class Vertex
{
    Point p;
public:
    Vertex(float x, float y, float z) : p{x, y, z} {}

    Point& coord() { return p; }
    const Point& coord() const { return p; }
};

int main()
{
    std::vector<Vertex> v;

    v.push_back(Vertex(-0.5, -0.5, 0.5));
    v.push_back(Vertex(0.5, -0.5, 0.5));
    v.push_back(Vertex(-0.5, 0.5, 0.5));
    v.push_back(Vertex(0.5, 0.5, 0.5));

    for (auto& p : v | coords) {
        p.x += 1;
    }

    for (const auto& p : v | coords) {
        std::cout << p.x << " " << p.y << " " << p.z << std::endl;
    }

    return 0;
}

Everything works fine on all the three major compilers when using old header and source files, or in a single main.cpp file: https://godbolt.org/z/M7a4a5YrP

The problems start when using C++20 modules on Clang 18: https://godbolt.org/z/T1cbzY1Mn

views.ixx:

module;

#include <ranges>

export module myviews;

namespace detail {

constexpr auto coord = [](auto&& p) -> decltype(auto) {
    return p.coord();
};

struct CoordsView
{
    constexpr CoordsView() = default;

    template<std::ranges::range R>
    friend constexpr auto operator|(R&& r, CoordsView)
    {
        return std::forward<R>(r) | std::views::transform(coord);
    }
};

} // namespace detail

export inline constexpr detail::CoordsView coords;

main.cpp:

#include <iostream>

#include <vector>

import myviews;

struct Point
{
    float x, y, z;
};

class Vertex
{
    Point p;
public:
    Vertex(float x, float y, float z) : p{x, y, z} {}

    Point& coord() { return p; }
    const Point& coord() const { return p; }
};

int main()
{
    std::vector<Vertex> v;

    v.push_back(Vertex(-0.5, -0.5, 0.5));
    v.push_back(Vertex(0.5, -0.5, 0.5));
    v.push_back(Vertex(-0.5, 0.5, 0.5));
    v.push_back(Vertex(0.5, 0.5, 0.5));

    for (auto& p : v | coords) {
        p.x += 1;
    }

    for (const auto& p : v | coords) {
        std::cout << p.x << " " << p.y << " " << p.z << std::endl;
    }

    return 0;
}

I am getting the error:

views.ixx:20:35: error: invalid operands to binary expression ('std::vector<Vertex>' and '_Partial<_Transform, decay_t<const (lambda at /app/views.ixx:9:24) &>>' (aka '_Partial<std::ranges::views::_Transform, detail::(lambda at /app/views.ixx:9:24)>'))
   20 |         return std::forward<R>(r) | std::views::transform(coord);
      |                ~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

which I don't understand.

On MSVC the code works, although I need to include #include <ranges> also on the main file (should this really be necessary?).

What am I doing wrong? Is there a better way to implement this kind of views?


Solution

  • I got it to work by making coord an inline variable (reasoning was: it's at namespace scope, so it's static).
    I'm not sure if it's well defined behaviour, though.

    Live on Compiler Explorer