Skip to content

Latest commit

 

History

History
605 lines (425 loc) · 6.7 KB

File metadata and controls

605 lines (425 loc) · 6.7 KB

Normal usage

std::

radr::

lvalues of borrowed ranges

lvalues of borrowed ranges

/* capture of lvalue borrow */
std::string_view sv = "foo";
auto vue1 = sv | std::views::take(2);

--

/* copy */
auto copy = vue1;

Well-formed ( $O(1)$ ).

/* capture of lvalue */
std::string_view sv = "foo";
auto rad2 = sv | radr::take(2);

Same syntax.

/* copy */
auto copy = rad1;

Well-formed ( $O(1)$ ).

lvalues of containers

lvalues of containers

/* capture of lvalue */
std::vector vec{1, 2, 3};
auto vue1 = vec | std::views::take(2);

--

/* copy */
auto copy = vue1;

Well-formed ( $O(1)$ ).

/* capture of lvalue */
std::vector vec{1, 2, 3};
auto rad2 = std::ref(vec) | radr::take(2);

You need to use std::ref() here!

/* copy */
auto copy = rad1;

Well-formed ( $O(1)$ ).

rvalues

rvalues

/* create adaptor on temporary */
auto vue0 = std::vector{1, 2, 3} | std::views::take(2);

/* move existing */
std::vector vec{1, 2, 3};
auto vue1 = std::move(vec) | std::views::take(2);

Same syntax.

/* copy */
// auto copy = vue1;

Ill-formed, owning views are not copyable.

/* create adaptor on temporary */
auto rad0 = std::vector{1, 2, 3} | radr::take(2);

/* move existing */
std::vector vec{1, 2, 3};
auto rad1 = std::move(vec) | radr::take(2);

Same syntax.

/* copy */
auto copy = rad1;

Well-formed; our owning adaptors are ( $O(n)$ ).

See the page on fundamental range properties for more details.

✂️ Dangling references

std::

radr::

dangling references

dangling references

{
  std::string_view v{"FOO"};
  /**/
  return v | std::views::take(2);
}

Well-formed; not dangling.

{
  std::string_view v{"FOO"};
  /**/
  return std::move(v) | std::views::take(2);
}

Well-formed; not dangling.

{
  std::vector v{1,2,3};
  /**/
  return v | std::views::take(2);
}

Well-formed, dangling 💣

{
  std::vector v{1,2,3};
  /**/
  return std::move(v) | std::views::take(2);
}

Well-formed, not dangling.

{
  std::string_view v{"FOO"};
  /**/
  return v | radr::take(2);
}

Well-formed, not dangling.

{
  std::string_view v{"FOO"};
  /**/
  return std::move(v) | radr::take(2);
}

Well-formed; not dangling.

// {
//   std::vector v{1,2,3};
//   /*…*/
//   return v | std::views::take(2);
// }

Ill-formed; dangling prevented.

{
  std::vector v{1,2,3};
  /**/
  return std::move(v) | std::views::take(2);
}

Well-formed; not dangling.

This library's syntax explicitly forces you to choose between std::move()-ing or std::ref()-ing existing variables into adaptors. This avoids unintendedly creating dangling references.

🪨 Const

std::

radr::

const-iterating

const-iterating

void print(auto const & r)
{
  for (char c : r)
    std::cout << c;
}

--

std::vector vec{'f','o','o'};
auto v = vec | std::views::take(2);
print(v);

Well-formed.

std::vector vec{'f','o','o'};
auto v = vec | std::views::transform(/**/);
print(v);

Well-formed.

std::vector vec{'f','o','o'};
auto v = vec | std::views::filter(/**/);
// print(v);

Ill-formed; filter not const-iterable.

void print(auto const & r)
{
  for (char c : r)
    std::cout << c;
}

--

std::vector vec{'f','o','o'};
auto v = vec | radr::take(2);
print(v);

Well-formed.

std::vector vec{'f','o','o'};
auto v = vec | radr::transform(/**/);
print(v);

Well-formed.

std::vector vec{'f','o','o'};
auto v = vec | radr::filter(/**/);
print(v);

Well-formed.

shallow const

deep const

void mutcon(auto const & r)
{
  for (char & c : r)
    c = 'A';
}

--

std::vector vec{'f','o','o'};
// mutcon(v);

Ill-formed; can't write through const.

std::vector vec{'f','o','o'};
auto v = vec | std::views::take(2);
mutcon(v);

Well-formed; vec is changed 🙈

void mutcon(auto const & r)
{
  for (char & c : r)
    c = 'A';
}

--

std::vector vec{'f','o','o'};
// mutcon(v);

Ill-formed; can't write through const.

std::vector vec{'f','o','o'};
auto v = vec | std::views::take(2);
// mutcon(v);

Ill-formed; can't write through const.

  • Many standard library views are not const-iterable; all our adaptors on containers are.
  • Standard library views allow writing through const; all our adaptors on containers forbid it.
  • See the page on const for more details.

💥 Undefined behaviour

std::

radr::

undefined behaviour

no undefined behaviour

std::vector vec{1,2,2,3}
auto is_even = /**/;

auto vue = vec | std::view::filter(is_even);
auto b = vue.begin(); // on first '2'
*b = 1; // no longer satisfies predicate

Undefined behaviour 💣

std::vector vec{1,2,2,3}
auto is_even = /**/;

auto rad = vec | radr::filter(is_even);
auto b = vue.begin(); // on first '2'
// *b = 1;

Ill-formed; our filter prevents changes to vec.

undefined behaviour

no undefined behaviour

std::vector vec{1,2,2,3}
auto plusCount = [count=0] (int i) mutable
{
  return i + count++;
};

auto vue = vec | std::view::transform(plusCount);
std::print("{}", vue[0]); // "1"
std::print("{}", vue[0]); // "2"

Violates the multi-pass guarantee. Undefined behaviour 💣

std::vector vec{1,2,2,3}
auto plusCount = [count=0] (int i) mutable
{
  return i + count++;
};

// auto rad = std::ref(vec) | radr::transform(plusCount);

Mutable function object is rejected; no undefined behaviour.1

See the page on undefined behaviour.

Footnotes

  1. Note that we cannot prevent all violations of the multi-pass guarantee, e.g. if the count variable is captured from outside, the functor need not be mutable to violate the guarantee. But catching some is better than catching none 😉 .