metalfox
metalfox

Reputation: 6731

std::ranges::elements_view for custom tuple-like data

I have an use case that can be reduced to:

#include <vector>
#include <ranges>
#include <tuple>

struct TDat
{
  double x, y;

  template <std::size_t I>
  friend double &get(TDat &Dat)
    { if constexpr (I == 0) return Dat.x; else return Dat.y; }

  template <std::size_t I>
  friend double const &get(TDat const &Dat)
    { if constexpr (I == 0) return Dat.x; else return Dat.y; }
};

namespace std
{

template <>
struct tuple_size<TDat> : integral_constant<size_t, 2u> {};

template <size_t I>
struct tuple_element<I, TDat>
  { using type = double; };

} // std

class TTable
{
private:
  std::vector<TDat> DatVec;

public:  
  auto GetxVec()
    { return std::views::keys(DatVec); }
};

That piece of code does not compile with G++ 10 because TDat does not model __has_tuple_element<std::ranges::range_value_t<_Vp>, _Nm>. The problem seems to be that this concept is defined as (https://github.com/gcc-mirror/gcc/blob/fab263ab0fc10ea08409b80afa7e8569438b8d28/libstdc%2B%2B-v3/include/std/ranges#L3318):

  namespace __detail
  {
    template<typename _Tp, size_t _Nm>
    concept __has_tuple_element = requires(_Tp __t)
      {
    typename tuple_size<_Tp>::type;
    requires _Nm < tuple_size_v<_Tp>;
    typename tuple_element_t<_Nm, _Tp>;
    { std::get<_Nm>(__t) }
      -> convertible_to<const tuple_element_t<_Nm, _Tp>&>;
      };
  }

I believe that, since the call to get is qualified, the compiler cannot find my get functions. Is that conclusion correct? If so, is that the intended behaviour? Is it defined behaviour to define my get functions in the std namespace?

Upvotes: 5

Views: 385

Answers (1)

Barry
Barry

Reputation: 302748

I believe that, since the call to get is qualified, the compiler cannot find my get functions. Is that conclusion correct?

Yes.

If so, is that the intended behaviour?

That's the specified behavior at least. [range.elements.view] defines the concept as:

  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>&>;
    };

where all functions calls in the standard library are implicitly fully qualified unless described otherwise [library.contents]/3:

Whenever a name x defined in the standard library is mentioned, the name x is assumed to be fully qualified as ​::​std​::​x, unless explicitly described otherwise.

There is no description otherwise here.

However, whether it's intended or not is a different question. And the answer there is also yes. The Library doesn't actually have a "TupleLike" concept yes. Yes, structured bindings exist, but there's no formal specification of that in the Library yet - so until that happens, all the tuple-like algorithms only work for standard library tuple-like things (pair, array, tuple, etc.)

Is it defined behaviour to define my get functions in the std namespace?

No, you may not.

Upvotes: 3

Related Questions