Search code examples
c++c++20initializer-liststd-ranges

Error when calling std::views::join on std::initializer_list of vectors


I want to join multiple views, without using temporary variables (like arr_and_zeroes in the example). However, doing so produces a compile error.
Is there any way to do it without using temporary variables?
I'm using g++ 14.2.1

#include <vector>
#include <ranges>
#include <type_traits>

int main() {
  std::vector<int> arr{2, 3, 7, 5};
  auto arr_and_zeroes = {{0}, arr, {0}};
  static_assert(std::is_same_v<
      decltype(arr_and_zeroes),
      std::initializer_list<std::vector<int>>
  >);

  auto r1 = std::views::join(arr_and_zeroes);
  // both of those assignments produce compile-time errors:
  //auto r2 = std::views::join({{0}, arr, {0}});
  //auto r3 = std::views::join(
  //    std::initializer_list<std::vector<int>>{{0}, arr, {0}}
  //);
}

Error message on r2 assignment:

bug2.cpp: In function ‘int main()’:
bug2.cpp:15:29: error: no match for call to ‘(const std::ranges::views::_Join) (<brace-enclosed initializer list>)’
   15 |   auto r2 = std::views::join({{0}, arr, {0}});
      |             ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~
In file included from bug2.cpp:2:
/usr/include/c++/14.2.1/ranges:3227:9: note: candidate: ‘template<class _Range>  requires (viewable_range<_Range>) && (__can_join_view<_Range>) constexpr auto
std::ranges::views::_Join::operator()(_Range&&) const’
 3227 |         operator() [[nodiscard]] (_Range&& __r) const
      |         ^~~~~~~~
/usr/include/c++/14.2.1/ranges:3227:9: note:   template argument deduction/substitution failed:
bug2.cpp:15:29: note:   couldn’t deduce template parameter ‘_Range’
   15 |   auto r2 = std::views::join({{0}, arr, {0}});
      |             ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~

Error message on r3 assignment:

bug2.cpp: In function ‘int main()’:
bug2.cpp:16:29: error: no match for call to ‘(const std::ranges::views::_Join) (std::initializer_list<std::vector<int> >)’
   16 |   auto r3 = std::views::join(
      |             ~~~~~~~~~~~~~~~~^
   17 |       std::initializer_list<std::vector<int>>{{0}, arr, {0}}
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   18 |   );
      |   ~                          
In file included from bug2.cpp:2:
/usr/include/c++/14.2.1/ranges:3227:9: note: candidate: ‘template<class _Range>  requires (viewable_range<_Range>) && (__can_join_view<_Range>) constexpr auto std::ranges::views::_Join::operator()(_Range&&) const’
 3227 |         operator() [[nodiscard]] (_Range&& __r) const
      |         ^~~~~~~~
/usr/include/c++/14.2.1/ranges:3227:9: note:   template argument deduction/substitution failed:
/usr/include/c++/14.2.1/ranges:3227:9: note: constraints not satisfied
In file included from /usr/include/c++/14.2.1/bits/ranges_util.h:34,
                 from /usr/include/c++/14.2.1/tuple:44,
                 from /usr/include/c++/14.2.1/bits/uses_allocator_args.h:39,
                 from /usr/include/c++/14.2.1/bits/memory_resource.h:41,
                 from /usr/include/c++/14.2.1/vector:87,
                 from bug2.cpp:1:
bug2.cpp: In substitution of ‘template<class _Range>  requires (viewable_range<_Range>) && (__can_join_view<_Range>) constexpr auto std::ranges::views::_Join::operator()(_Range&&) const [with _Range = std::initializer_list<std::vector<int> >]’:
bug2.cpp:16:29:   required from here
   16 |   auto r3 = std::views::join(
      |             ~~~~~~~~~~~~~~~~^
   17 |       std::initializer_list<std::vector<int>>{{0}, arr, {0}}
      |       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   18 |   );
      |   ~                          
/usr/include/c++/14.2.1/bits/ranges_base.h:808:13:   required for the satisfaction of ‘viewable_range<_Range>’ [with _Range = std::initializer_list<std::vector<int, std::allocator<int> > >]
/usr/include/c++/14.2.1/bits/ranges_base.h:810:11: note: no operand of the disjunction is satisfied
  809 |       && ((view<remove_cvref_t<_Tp>> && constructible_from<remove_cvref_t<_Tp>, _Tp>)
      |          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  810 |           || (!view<remove_cvref_t<_Tp>>
      |           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  811 |               && (is_lvalue_reference_v<_Tp>
      |               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  812 |                   || (movable<remove_reference_t<_Tp>>
      |                   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  813 |                       && !__detail::__is_initializer_list<remove_cvref_t<_Tp>>))));
      |                       ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc1plus: note: set ‘-fconcepts-diagnostics-depth=’ to at least 2 for more detail


Solution

  • It seems like join must be passed a viewable_range, with its concept outlined here:

    template< class T >
    concept viewable_range =
        ranges::range<T> &&
        ((ranges::view<std::remove_cvref_t<T>> &&
          std::constructible_from<std::remove_cvref_t<T>, T>) ||
         (!ranges::view<std::remove_cvref_t<T>> &&
          (std::is_lvalue_reference_v<T> ||
           (std::movable<std::remove_reference_t<T>> && !/*is-initializer-list*/<T>))));
    

    Referencing the last line, initializer lists are explicitly disallowed unless std::remove_cvref_t<T> is a specialization of std::initializer_list.

    You can see cppreference to see an example of viable viewable_ranges that would work.

    If you are using C++26 you can use std::ranges::views::concat instead:

    int main() {
        using namespace std::views;
        std::vector<int> arr{2, 3, 7, 5};
        auto cat = concat(single(0), arr, single(0));
    }
    

    If you aren't using C++26, creating a temporary and using join is probably the simplest solution without implementing concat yourself.