Могу ли я заставить `std::ranges::views::elements` работать с диапазоном моего типа

Рассмотрим тип Point со значениями x, y и z. Если у меня есть диапазон объектов Point, например std::vector<Point>, что мне нужно добавить к Point, чтобы он работал с адаптером диапазона std::ranges::views::elements?

Намерение состоит в том, чтобы сделать что-то вроде

std::vector<Point> v{...};
for (auto x : v | std::ranges::views::elements<0>) {
    // do something with all `x` values
}

В документации упоминается, что std::ranges::views::elements работает со значениями, подобными кортежам. Я предположил, что это должно работать аналогично тому, как мы можем сделать наш тип работает со структурированной привязкой, но, похоже, я что-то упускаю

Я пробовал со следующим кодом

class Point {
    double x=0;
    double y=0;
    double z=0;
public:
    Point(double x, double y, double z) : x(x), y(y), z(z) {}

    template <std::size_t N>
    double get() const {
        if constexpr(N == 0)
            return x;
        else if constexpr(N == 1)
            return y;
        else if constexpr(N == 2)
            return z;
    }
};

namespace std {

template <>
struct tuple_size<Point> : std::integral_constant<std::size_t, 3> {};

template <std::size_t N>
struct tuple_element<N, Point> {
    using type = double;
};
}

Этого достаточно, чтобы структурированное связывание заработало, но std::ranges::views::elements по-прежнему не работает. Затем я подумал, что, возможно, std::ranges::views::elements нужно std::get<n>(p) для работы, и я добавил специализацию ниже в пространство имен std.

template <std::size_t N>
double get(const Point &p) {
    if constexpr(N == 0)
        return p.get<0>();
    else if constexpr(N==1)
        return p.get<1>();
    else if constexpr(N==2)
        return p.get<2>();
}

Теперь можно использовать std::get‹0›(p) для извлечения значения x, но опять же этого недостаточно для std::ranges::views::elements. Что еще нужно, чтобы диапазон Point объектов работал с std::ranges::views::elements?


PS: Я знаю, что мог бы просто использовать здесь views::transform, но моя главная цель здесь - быть общим и понять, как эти вещи должны сочетаться друг с другом.


person darcamo    schedule 28.05.2021    source источник
comment
Можете ли вы предоставить сообщение об ошибке, которое вы получаете?   -  person Nicol Bolas    schedule 28.05.2021
comment
В качестве альтернативы можно предоставить функцию-член для преобразования Point в tuple, содержащую ссылки на его элементы, godbolt.org/z/ Kxnsdn1bM.   -  person 康桓瑋    schedule 28.05.2021


Ответы (2)


Могу ли я заставить std::ranges::views::elements работать с диапазоном моего типа

Нет, нельзя.

Способ указания elements в [range.elements.view] , он ограничен:

  template<class T, size_t N>
  concept has-tuple-element =                   // exposition only
    requires(T t) {
      typename tuple_size<T>::type;
      requires N < tuple_size_v<T>;
      typename tuple_element_t<N, T>;
      { get<N>(t) } -> convertible_­to<const tuple_element_t<N, T>&>;
    };

но мы должны помнить об общем правиле в библиотеке, из [contents]/ 3 что:

Всякий раз, когда упоминается имя x, определенное в стандартной библиотеке, предполагается, что имя x полностью определено как ::​std​::​x, если явно не указано иное. Например, если элемент Effects: для библиотечной функции F описан как вызывающий библиотечную функцию G, имеется в виду функция ::​std​::​G.

get<N>(t) не является безоговорочным вызовом get, это вызов ::std::get<N>(t) (нет, если явно не указано иное).

Это означает, что тестируется std::get<0> (для keys), и он не найдет поддержки структурированных привязок, предоставляемых пользователями (которые должны быть либо членом e.get<0>(), либо неполным get<0>(e) в связанном пространстве имен). Вы не можете просто добавить перегрузки в std, чтобы все заработало.

Итак... в настоящее время не поддерживается.


Технически, если вы поместите свою перегрузку std::get<N>(Point) в пространство имен std и убедитесь, что оно определено до включения <ranges>, это будет Just WorkTM . Но это очень хрупко, потому что вам нужно тщательно контролировать порядок включения (что вы не можете сделать) и включает в себя добавление перегрузок в std (чего вам также не следует делать, особенно в этом случае, когда эти перегрузки не будут работать). в любом случае не помогают структурированные привязки).

person Barry    schedule 28.05.2021
comment
Я думал об этом, но эта функция казалась настолько полезной, что я решил, что это недостаток! Я не могу найти обсуждения этого ограничения, которое, по-видимому, восходит к вторая редакция P0789, которая, к сожалению, не содержит абсолютно никаких объяснений. - person Davis Herring; 28.05.2021
comment
@DavisHerring elements был добавлен P1035, но проблема здесь в том, что нет библиотечного протокола для кортежей ... поэтому он просто поддерживает только библиотечные кортежи. Плюс ADL get просто... проблема. - person Barry; 28.05.2021
comment
Я проверил хрупкое решение, и оно работает именно так, как вы упомянули. Но на самом деле он очень хрупкий. Сейчас я буду использовать views::transform всякий раз, когда мне это нужно, вместо того, чтобы заставить мой тип Point работать с общим views::elements. - person darcamo; 28.05.2021

Цель состоит в том, чтобы вы предоставили шаблон функции get, не являющийся членом, в вашем пространстве имен (для ADL). Это является более строгим, чем структурированные привязки, которые также поддерживают элемент get.

Ваша специализация не сработала, потому что это не специализация: это перегрузка, поэтому она запрещена в пространстве имен std (и на практике не была найдена при поиске по имени из <ranges>).

person Davis Herring    schedule 28.05.2021
comment
Я также пытался использовать функцию get, не являющуюся членом, но это не сработало. Кажется, что ответ @Barry правильный, а вызов get<N>(Point) уточняется с помощью std::, что, таким образом, не позволяет ADL найти мою реализацию get, не являющуюся членом. - person darcamo; 28.05.2021