This works in Clang...

  template<class...T>struct Over:T...,std::true_type{};
  template<class...T> Over(T...) -> Over<T...>;

  template <typename T>
  concept rangeforable = Over{[]{for(auto&&x:T{})(void)x;}};

  static_assert( ! rangeforable<int> );
  static_assert( rangeforable<int[2]> );
  static_assert( rangeforable<std::initializer_list<int>> );

On Thu, Dec 9, 2021 at 9:24 PM Jason McKesson via Std-Discussion <> wrote:
On Thu, Dec 9, 2021 at 12:31 PM Andrew Schepler via Std-Discussion
<> wrote:
> I recently wanted a way to determine whether or not an expression can appear to the right of ":" in the range-based for syntax, and what the type of the element (*__begin) is if so.

Why do you specifically need to know if it can appear in a range-based
`for` loop?

The C++20 concept `std::ranges::range<T>` detects whether `T` is a
range. Anything which is a C++20 range can be used in a range-based
`for` loop. Now the reverse is not true: many types which are not
C++20 ranges can work in range-based `for`.

But why would you want to allow "technically valid" code to work?

Also, `std::ranges::iterator_t<Range>` results in the iterator type
for the range. This is what `std::ranges::begin()` returns. But this
requires that `Range` is a C++20 range type.

Your code in C++20 would be written as:

namespace ranges = std::ranges

class Thing {...};

class ThingCollection
  template<std::convertible_to<Thing> T>
  void add(T &&);

  template<ranges::range Rng>
    requires std::convertible_to<ranges::value_t<Rng>, Thing>
  void add(Rng &&rng)
    for(auto &&elem: rng)


> Still, would it maybe be worth a standard proposal to provide a truly correct way to do this, plus make it easier than all that code imitating what a compiler needs to do internally anyway?

No. We don't want people to write code with such simplistic semantics.
Ranges should be *ranges*, not just things with begin/end on them.
Std-Discussion mailing list