6

I am trying to understand, for what reasons does the yield family of functions require that class be default constructible?

In the following example, the vnums1 line compiles only if CNum has a default constructor. The vnums2 line does not require a default constructor.

I am using Visual Studio 2017 and Range-V3-VS2015. Thank you!

#include <range/v3/all.hpp>

struct CNum
{
    // CNum() = default;
    explicit CNum(int num) : m_num(num) {}
    int m_num;
};

int main()
{
    auto ints = ranges::view::ints(0, 10);

    // this compiles only of CNum has a default constructor
    auto vnums1 = ints
        | ranges::view::for_each([](int num) { return ranges::yield_if(num % 2, CNum(num)); })
        | ranges::to_vector;

    // this compiles even if CNum does not have a default constructor
    auto vnums2 = ints
        | ranges::view::remove_if([](int num) { return num % 2 == 0; })
        | ranges::view::transform([](int num) { return CNum(num); })
        | ranges::to_vector;

    return 0;
}

2 Answers2

6

We just changed the code to not require DefaultConstructible. git pull and enjoy.

Eric Niebler
  • 5,927
  • 2
  • 29
  • 43
  • That's great. Thank you. Maintainers of Microsoft/Range-V3-VS2015 are not picking up new changes from ericniebler/range-v3. Does anybody have suggestions on how I can go about getting the latest bits to work with VC++ 2017? – CodeAndLearn Feb 21 '17 at 21:55
  • Sadly, your best bet is to fork the Microsoft/Range-V3-VS2015 repo and make the changes yourself. – Eric Niebler Mar 01 '17 at 09:22
2

The reason you need to default constructor to use ranges::yield_if is that the machinery it uses requires the type to be default constructable. If we look at the code we have

struct yield_if_fn
{
    template<typename V>
    repeat_n_view<V> operator()(bool b, V v) const
    {
        return view::repeat_n(std::move(v), b ? 1 : 0);
    }
};

/// \relates yield_if_fn
/// \ingroup group-views
RANGES_INLINE_VARIABLE(yield_if_fn, yield_if)

And we can see that it calls view::repeat_n. Looking at that code we get

repeat_n_view<Val> operator()(Val value, std::ptrdiff_t n) const
{
    return repeat_n_view<Val>{std::move(value), n};
}

And if we look at repeat_n_view we have

// Ordinarily, a view shouldn't contain its elements. This is so that copying
// and assigning ranges is O(1), and also so that in the event of element
// mutation, all the copies of the range see the mutation the same way. The
// repeat_n_view *does* own its lone element, though. This is OK because:
//  - O(N) copying is fine when N==1 as it is in this case, and
//  - The element is immutable, so there is no potential for incorrect
//    semantics.

struct repeat_n_view
  : view_facade<repeat_n_view<Val>, finite>
{
private:
    friend range_access;
    Val value_;
    std::ptrdiff_t n_;

    // ...
public:
    repeat_n_view() = default;
    constexpr repeat_n_view(Val value, std::ptrdiff_t n)
      : value_(detail::move(value)), n_((RANGES_EXPECT(0 <= n), n))
    {}
    constexpr std::size_t size() const
    {
        return static_cast<std::size_t>(n_);
    }
};

We see from the comment that this was a design decision and because of this design you need your type to be default constructable. Eric describes the type required as being SemiRegular which is documented as

it needs to be default constructable, copy and move constructable, and destructable.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402