By Davidbrcz


2019-02-11 07:33:03 8 Comments

In this blog post, Eric Niebler states that:

What is wrong with std::begin and std::end? Surprise! they are not memory safe. Consider what this code does:

extern std::vector<int> get_data();
auto it = std::begin(get_data());
int i = *it; // BOOM

std::begin has two overloads for const and non-const lvalues. Trouble is, rvalues bind to const lvalue references, leading to the dangling iterator it above.

I'm having trouble understanding his point and why it is a dangling reference. Could someone explain?

6 comments

@lisuwang 2019-02-14 09:37:02

in fact , the complicated function can be simplified to two short functions , such as

int& foo(int x){
   return x;
}
int generate_a_int(){
   return 42;
}

and then invoke it foo(generate_a_int()), a temporary value is generated , once out of the function body, the temporary value generated by generate_a_int() is destroy , and then dangle reference is happened ...

do you understand now?

@ANone 2019-02-12 13:39:17

Because it allows initialisation from an rvalue, which is bad.

@StoryTeller 2019-02-11 08:47:32

I think Eric's point about std::begin is that it silently accepts an rvalue container as an argument to begin with. On the face of it, the problem with the code is also exemplified in

auto it = get_data().begin();

But std::begin is a free function template, it can be made to reject rvalues without needing to add the proper reference qualifiers to each container's begin members. By "just" forwarding it misses an opportunity to add a layer of memory safety to code.

Ideally, the overload set could have benefited from the addition of

template< class C > 
void begin( C&& ) = delete;

That one would have caused the code in the blog post to be flat out rejected on the spot.

@Paul Stelian 2019-02-11 09:51:22

Is it possible for us to make a warning of deprecation or usage for that particular template instantiation instead of outright deleting it? Compiler specific extensions allowed.

@StoryTeller 2019-02-11 09:52:54

@PaulStelian - Sure, I think so. It's just another overload, so it could be defined however.

@StoryTeller 2019-02-11 10:01:38

@PaulStelian - Here's how btw, pure C++14. coliru.stacked-crooked.com/a/7c99059b5b1a8dca

@Paul Stelian 2019-02-11 11:23:06

Forgot that C++14 actually has built in options for deprecating...

@Paul Stelian 2019-02-11 11:24:06

I assume you can't just add the specialization on the std::begin thingy right? But yeah, nice.

@StoryTeller 2019-02-11 11:25:23

@PaulStelian - No, we can't. I intentionally provided an example in another namespace. To add this overload, we'd need to write a proposal, then get it voted int o the standard.

@YSC 2019-02-11 12:24:14

The thing is, as Alexandrescu points it out with humour in its 2018 cppcon talk, the C++ committee likes to be consistent in its past errors. One might think the reason why std::begin accepts r-values is to let begin(c) and c.begin() be perfectly equivalent. (but yes obviously this overload set would be ideal)

@The Vee 2019-02-11 18:00:47

@YSC .begin() could also be made to reject rvalues, couldn't it? iterator begin() && = delete;

@Joshua Green 2019-02-13 00:38:36

The analogous overload for std::as_const is defined as namespace std {template <class T> void as_const(const T &&) = delete;}.

@drjpizzle 2019-02-12 00:37:04

Is it fair to say this is a memory safety failing of std::begin? There is no 'safe' way of creating an iterator such that it will be valid to follow the pointer after the container is deleted.

It lacks a check that another method has, but it's not like there is no way to make a ranges derived iterator go pop. You just have to try a bit harder...

@Jesper Juhl 2019-02-11 07:38:16

The temporary vector returned by get_data goes out of scope after std::begin is done. It is not kept alive, so it is an iterator into a destroyed object.

@Some programmer dude 2019-02-11 07:37:09

The get_data function returns an object. When used the way shown, that object will be a temporary object, which will be destructed once the full expression ends. The iterator now references a vector object which no longer exists, and can't be dereferenced or used in any useful way.

@Benjamin Bihler 2019-02-11 08:35:01

Though this is definitely correct, isn't john's comment to the original post also appropriate? How does the given example show a problem of std::begin and std::end?

@Eric Niebler 2019-02-11 16:17:00

Because std::begin lets you make that mistake, but std::ranges::begin will not.

Related Questions

Sponsored Content

23 Answered Questions

14 Answered Questions

[SOLVED] Why can templates only be implemented in the header file?

10 Answered Questions

35 Answered Questions

[SOLVED] Why is "using namespace std" considered bad practice?

21 Answered Questions

[SOLVED] Why should I use a pointer rather than the object itself?

  • 2014-03-03 11:54:16
  • gEdringer
  • 274304 View
  • 1425 Score
  • 21 Answer
  • Tags:   c++ pointers c++11

3 Answered Questions

[SOLVED] Why const double && doesn't work for lvalue reference?

1 Answered Questions

Seeking clarification on std::forward

1 Answered Questions

[SOLVED] C++11 binding rules for const &&

1 Answered Questions

Sponsored Content