5

The libstdc++ implementation of std::ranges::ref_view, following the C++ standard, includes the following code:

  template<range _Range> requires is_object_v<_Range>
    class ref_view : public view_interface<ref_view<_Range>>
    {
    private:
      _Range* _M_r;

      static void _S_fun(_Range&); // not defined
      static void _S_fun(_Range&&) = delete;

    public:
      template<__detail::__different_from<ref_view> _Tp>
        requires convertible_to<_Tp, _Range&>
          && requires { _S_fun(declval<_Tp>()); }
        constexpr
        ref_view(_Tp&& __t)
        noexcept(noexcept(static_cast<_Range&>(std::declval<_Tp>())))
          : _M_r(std::__addressof(static_cast<_Range&>(std::forward<_Tp>(__t))))
        { }
    };

There are three constraints on the constructor:

  1. __detail::__different_from<ref_view>
  2. convertible_to<_Tp, _Range&>
  3. requires { _S_fun(declval<_Tp>()); }

My questions are:

  1. Is Constraint 2 stricter than Constraint 1, and is Constraint 3 stricter than Constraint 2? In other words, could we keep only Constraint 3 and remove the other two without changing the behavior?
  2. Since std::is_object_v<_Range> ensures _Range is an object type, is the forwarding reference _Tp&& necessary? Would the following simpler non-template constructor suffice?
  template<range _Range> requires is_object_v<_Range>
    class ref_view : public view_interface<ref_view<_Range>>
    {
    private:
      _Range* _M_r;

    public:
        constexpr
        ref_view(_Range& __t)
        noexcept
          : _M_r(std::__addressof(__t))
        { }
    };

I tried three AI models (Gemini, Grok, and Claude), and their answers contain various mistakes. For example, they incorrectly think that using non-template constructor would restrict the constructor to exact matches, preventing polymorphic use.

3
  • convertivle_to allows same types, constraint 3 seems indeed stricter than constraint 2 (and no subsumption seems involved) Commented Sep 2 at 8:21
  • I think you need the deleted version ref_view(_Range&&) = delete; for types with multiple conversion operator. Commented Sep 2 at 8:25
  • __different_from is needed to not compete with ref_view's own copy and move constructors. Commented Sep 2 at 8:41

1 Answer 1

8
  • __detail::__different_from<ref_view> is necessary to ensure that this template constructor, i.e, ref_view(_Tp&&), does not eat the default copy and move constructors of ref_view. In other words, this constructor should not be selected when copying or moving ref_view.
  • convertible_to<_Tp, _Range&> is necessary to ensure that the type _Tp (not necessarily a range type) meets both the syntactic and semantic requirements of being explicitly or implicitly converted to _Range&, as the constructor explicitly converts __t to R& via static_cast.
  • The well-formedness requirement for the expression _S_fun(declval<_Tp>()) arises from a similar resolution in LWG 2993 that applies to ref_view.

Since std::is_object_v<_Range> ensures _Range is an object type, is the forwarding reference _Tp&& necessary? Would the following simpler non-template constructor suffice?

This is not enough, you also need ref_view(_Range&& __t) = delete to prevent ref_view<const vector<int>> from accidentally taking an rvalue reference of vector, since const vector<int>& can bind to an rvalue:

template<range _Range>
  requires is_object_v<_Range>
class ref_view : public view_interface<ref_view<_Range>> {
 private:
  _Range* _M_r;

 public:
  constexpr ref_view(_Range& __t) noexcept : _M_r(std::__addressof(__t)) { }
  
  ref_view(_Range&&) = delete;
};

However, this is precisely the potential issue described by LWG 2993: a deleted constructor can still be used to form implicit conversion sequences. So the following will undesirably lead to ambiguity:

void fun(ranges::ref_view<string>); //#1
void fun(string_view);              //#2

string get_string();

fun(get_string()); // error: call of overloaded 'fun(std::string)' is ambiguous

Following a similar solution from LWG 2993, this ultimately led to the constructor becoming a template that takes a forwarding reference as it does today.

Sign up to request clarification or add additional context in comments.

2 Comments

Won't _S_fun(declval<_Tp>()) also check for conversion, and can supersede convertible_to<_Tp, _Range&>?
Thats only check for the implicit conversion not explicit ones.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.