1

This code works and generates the correct output (23, as 2 and 3 are the sizes of the two std::vector<int>s in array_of_vectors:

#include <array>
#include <boost/range/adaptor/transformed.hpp>
#include <iostream>
#include <vector>

using boost::adaptors::transformed;

constexpr auto get_size = [](auto const& snip){ return snip.size(); };

int main() {
    std::array<std::vector<int>,2> array_of_vectors = {
        {std::vector<int>{1,1}, std::vector<int>{1,1,1}}
    };

    auto array_of_sizes = array_of_vectors | transformed(get_size);
    for (auto i : array_of_sizes) {
        std::cout << i;
    }
}

On the other hand, if I try to enforce that array_of_sizes is indeed a std::array<std::size_t, 2u> via boost::copy_range by changing this

auto array_of_sizes = array_of_vectors | transformed(get_size);

to this

auto array_of_sizes = boost::copy_range<std::array<std::size_t, 2u>>(
        array_of_vectors | transformed(get_size)
        );

I get the following error,

$ g++ -std=c++17 deleteme.cpp && ./a.out 
In file included from /usr/include/boost/range/iterator_range.hpp:13,
                 from /usr/include/boost/range/adaptor/transformed.hpp:16,
                 from deleteme.cpp:2:
/usr/include/boost/range/iterator_range_core.hpp: In instantiation of ‘SeqT boost::copy_range(const Range&) [with SeqT = std::array<long unsigned int, 2>; Range = boost::range_detail::transformed_range<<lambda(const auto:1&)
 2> >]’:
deleteme.cpp:16:114:   required from here
/usr/include/boost/range/iterator_range_core.hpp:842:20: error: no matching function for call to ‘std::array<long unsigned int, 2>::array(boost::range_detail::extract_const_iterator<boost::range_detail::transformed_range<<la
y<std::vector<int>, 2> >, true>::type, boost::range_detail::extract_const_iterator<boost::range_detail::transformed_range<<lambda(const auto:1&)>, std::array<std::vector<int>, 2> >, true>::type)’
  842 |             return SeqT( boost::begin( r ), boost::end( r ) );
      |                    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from deleteme.cpp:1:
/usr/include/c++/10.2.0/array:94:12: note: candidate: ‘std::array<long unsigned int, 2>::array()’
   94 |     struct array
      |            ^~~~~
/usr/include/c++/10.2.0/array:94:12: note:   candidate expects 0 arguments, 2 provided
/usr/include/c++/10.2.0/array:94:12: note: candidate: ‘constexpr std::array<long unsigned int, 2>::array(const std::array<long unsigned int, 2>&)’
/usr/include/c++/10.2.0/array:94:12: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/10.2.0/array:94:12: note: candidate: ‘constexpr std::array<long unsigned int, 2>::array(std::array<long unsigned int, 2>&&)’
/usr/include/c++/10.2.0/array:94:12: note:   candidate expects 1 argument, 2 provided

On the other hand, most surprisingly to me (!), if I force array_of_sizes to be a std::vector<std::size_t>, e.g.

auto array_of_sizes = boost::copy_range<std::vector<std::size_t>>( // vector instead of array
        array_of_vectors | transformed(get_size)
        );

it works! It looks like transformed turns a std::array<T,N> into a range convertible to std::vector<T>, just like it loses track of std::array's compile-time size.

What is this error/behavior due to?

I can only think that something is not working because of std::array's compile-time known size, as opposed to std::vector's run-time size. Probably transformed can't deal with that? Or maybe it's copy_range which can't?

Enlico
  • 23,259
  • 6
  • 48
  • 102
  • What about `boost::range::copy`? Have you tried it too? The problem is that the `array_of_vectors`'s elements are `std::vector`s. Try making `array_of_vectors`'s elements `std::array`s. – NutCracker Dec 10 '20 at 09:29
  • Well, it does work, but it also forces me to create that `array_of_size` beforehand so that I can pass it as a second argument, whereas it would be more useful if it was a returned value, so that I can pass it to other functions directly if I need. However, your suggestion reinforces my understanding that my solution doesn't work because the size of `transformed`'s output is not made known at compile time (even if it could be, because the input array's size _is_ know at compile-time). – Enlico Dec 10 '20 at 09:39
  • So does my comment answers your question? Can I turn it to the actual answer? – NutCracker Dec 10 '20 at 09:40
  • @NutCracker, well you certainly can, and I'll +1 it, but before accepting I'd like to know _why_ `copy_range` doesn't work and if there are "input-output" solutions rather than "mutated input" solutions. – Enlico Dec 10 '20 at 09:44

1 Answers1

3

If you compile with -std=c++2a, gcc 10.2 has a nice error message:

boost_1_74_0/boost/range/iterator_range_core.hpp:842:38: error: array must be initialized with a brace-enclosed initializer
  842 |             return SeqT( boost::begin( r ), boost::end( r ) );
      |                          ~~~~~~~~~~~~^~~~~

The problem is that boost::copy_range only knows how to construct containers that take an iterator pair as (one of) their constructor parameters, so it is incompatible with std::array which does not actually have any constructors - it is only constructible as an aggregate from a (braced) list of elements.

With C++20's lambdas, you could write an alternate function that uses simple metaprogramming to produce that list of elements:

template<class T, class R>
T copy_range_fixed_size(R const& r) {
    return [&r]<std::size_t... I>(std::index_sequence<I...>) {
        auto it = boost::begin(r);
        return T{(I, *it++)...};
    }(std::make_index_sequence<T{}.size()>{});
}

Example.

Enlico
  • 23,259
  • 6
  • 48
  • 102
ecatmur
  • 152,476
  • 27
  • 293
  • 366