Return C++ iterator reference - c++

Example in which it does work as expected
#include <iostream>
#include <vector>
struct MyClass{
const std::vector<float>::iterator& begin(){
return myvec.begin();
}
const std::vector<float>::iterator& end(){
return myvec.end();
}
std::vector<float> myvec;
};
int main(){
std::vector<float> mainvec(8,0);
MyClass myClass;
myClass.myvec = mainvec;
for (std::vector<float>::iterator it = myClass.begin();
it != myClass.end();++it){
std::cout << *it << " " ;
}
std::cout << std::endl;
}
In this code I get the following output:
0 0 0 0 0 0 0 0
An example which does NOT works as expected:
#include <iostream>
#include <vector>
struct MyClass{
const std::vector<float>::iterator& begin(){
return myvec.begin();
}
const std::vector<float>::iterator& end(){
return myvec.end();
}
std::vector<float> myvec;
};
int main(){
std::vector<float> mainvec(8,0);
MyClass myClass;
myClass.myvec = mainvec;
const std::vector<float>::iterator& end_reference = myClass.end();
for (std::vector<float>::iterator it = myClass.begin();
it != end_reference;++it){
std::cout << *it << " " ;
}
std::cout << std::endl;
}
In this code I get the following output:
"empty output"
The first code example
It has the problem in which I invoke (erroneously) the vector begin() and end() instead of MyClass methods.
I have the following minimal code to represent my doubt:
#include <iostream>
#include <vector>
struct MyClass{
const std::vector<float>::iterator& begin(){
return myvec.begin();
}
const std::vector<float>::iterator& end(){
return myvec.end();
}
std::vector<float> myvec;
};
int main(){
std::vector<float> mainvec(8,0);
MyClass myClass;
myClass.myvec = mainvec;
for (std::vector<float>::iterator it = myClass.myvec.begin();
it != myClass.myvec.end();++it){
std::cout << *it << " " ;
}
std::cout << std::endl;
}
I get the following warnings on lines 8 and 12:
returning reference to local temporary object [-Wreturn-stack-address] [cpp/gcc]
but when I compile and run the program, I get:
0 0 0 0 0 0 0 0
So, it seems that the local reference is not destroyed when it returns the myvec.begin(). When I first wrote the code, I didn't thought it was going to be a problem, as in my head the begin() method from the vector would return a iterator reference to the first vector place, this iterator for me is not being allocated when I do myvec.begin(), but is a reference to this iterator. So, this warning shouldn't appear, as I am not allocating memory. But since I don't know how this mechanism works, I would like to learn it for writing consistent code. It seems that I can ignore this warning, can't I?

std::vector::begin returns indeed a temporary object
(see http://www.cplusplus.com/reference/vector/vector/begin/)
Your code is valid since you are binding the temporary object to a const reference (would not be legal with a non const reference), and copy it right away in a local variable it.
edit: My mistake. This is indeed also UB to return a const reference from a temporary.
However assigning this reference to another const reference with a longer lifetime would result in undefined behavior as the lifetime of a temporary object is only extended to the lifetime of the const reference it is directly assigned to.
I would recommend taking into account the warning and return an iterator.
I also recommend reading http://herbsutter.com/2008/01/01/gotw-88-a-candidate-for-the-most-important-const/

Related

C++ constexpr constructor initializes garbage values

I wanted a simple class which would encapsulate a pointer and a size, like C++20's std::span will be, I think. I am using C++ (g++ 11.2.1 to be precise)
I want it so I can have some constant, module-level data arrays without having to calculate the size of each one.
However my implementation 'works' only sometimes, dependent on the optimization flags and compiler (I tried on godbolt). Hence, I've made a mistake. How do I do this correctly?
Here is the implementation plus a test program which prints out the number of elements (which is always correct) and the elements (which is usually wrong)
#include <iostream>
#include <algorithm>
using std::cout;
using std::endl;
class CArray {
public:
template <size_t S>
constexpr CArray(const int (&e)[S]) : els(&e[0]), sz(S) {}
const int* begin() const { return els; }
const int* end() const { return els + sz; }
private:
const int* els;
size_t sz;
};
const CArray gbl_arr{{3,2,1}};
int main() {
CArray arr{{1,2,3}};
cout << "Global size: " << std::distance(gbl_arr.begin(), gbl_arr.end()) << endl;
for (auto i : gbl_arr) {
cout << i << endl;
}
cout << "Local size: " << std::distance(arr.begin(), arr.end()) << endl;
for (auto i : arr) {
cout << i << endl;
}
return 0;
}
Sample output:
Global size: 3
32765
0
0
Local size: 3
1
2
3
In this case the 'local' variable is correct, but the 'global' is not, should be 3,2,1.
I think the issue is your initialization is creating a temporary and then you're storing a pointer to that array after it has been destroyed.
const CArray gbl_arr{{3,2,1}};
When invoking the above constructor, the argument passed in is created just for the call itself, but gbl_arr refers to it after its life has ended. Change to this:
int gbl_arrdata[]{3,2,1};
const CArray gbl_arr{gbl_arrdaya};
And it should work, because the array it refers to now has the same lifetime scope as the object that refers to it.

insertion into unordered_map got lost

I'm having the following code, but after run the code, the result is empty, any ideas why the result is empty? the reference of result in function main was passed to myclass, I thought function addToResult will actually add data to result, and I'm expecting a map key = "test", value = "1": "1". I'm kind of new to c++. Thanks!
#include <iostream>
#include <string>
#include <unordered_map>
using LookUpTable = std::unordered_map<std::string, std::string>;
using DLTable = std::unordered_map<std::string, LookUpTable>;
class MyClass
{
public:
MyClass(DLTable& dltable) {
m_dltable = dltable;
};
void addToResult() {
LookUpTable ee;
ee.emplace("1", "1");
m_dltable.emplace("test", ee);
};
private:
DLTable m_dltable;
};
int main ()
{
DLTable result;
MyClass myclass(result);
myclass.addToResult();
std::cout << "myrecipe contains:" << std::endl;
for (auto& x: result) {
std::cout << x.first << ": "<< std::endl;
for (auto& xx : x.second) {
std::cout << xx.first << ": " << xx.second << std::endl;
}
}
std::cout << std::endl;
return 0;
}
Let' look into simplified example:
int a = 0;
int &b = a;
int c = b;
c = 123;
Will last assignment modify a? Of course not. It does not matter how you pass value to c through reference or not c is completely independent variable that just initialized by a reference.
Your case is the same - m_dltable is separate variable and the fact you initialize it using reference does not change anything. (Your case even worse, you did not initialize it by reference, you assigned to it)
In general your approach is wrong. If you want directly access that variable then just make it public, do not try to create convoluted workarounds on how to access it. If you want incapsulation just create members that allow you to iterate over that container. For example return a const reference to it or have begin() and end() methods that return (const) iterators accordingly.

How do I check whether a reference is const?

I was writing a test for my iterator types and wanted to check that the reference returned by de-referencing iterators provided by begin() and cbegin() are non-const and const respectively.
I tried doing something similar to the following : -
#include <type_traits>
#include <iostream>
#include <vector>
int main() {
std::vector<int> vec{0};
std::cout << std::is_const<decltype(*vec.begin())>::value << std::endl;
std::cout << std::is_const<decltype(*vec.cbegin())>::value << std::endl;
}
But this prints 0 for both cases.
Is there a way to check if a reference is const?
I can use C++11/14/17 features.
Remove the reference to get the referenced type to inspect its constness. A reference itself is never const - even though references to const may colloquially be called const references:
std::is_const_v<std::remove_reference_t<decltype(*it)>>
*it will be a reference rather than the referenced type (int& or const int& rather than int or const int in your case). So, you need to remove the reference:
#include <iostream>
#include <type_traits>
#include <vector>
int main() {
std::vector<int> vec{0};
std::cout << std::is_const<std::remove_reference<decltype(*vec.begin())>::type>::value << std::endl;
std::cout << std::is_const<std::remove_reference<decltype(*vec.cbegin())>::type>::value << std::endl;
}
This produces:
0
1
Note: The above works uses C++11. #eerorika's answer is more terse, but requires C++17.
is_const always returns false for references. Instead, do:
std::is_const_v<std::remove_reference_t<decltype(*v.begin() )>> // false
std::is_const_v<std::remove_reference_t<decltype(*v.cbegin())>> // true
You can check the notes on document here:
https://en.cppreference.com/w/cpp/types/is_const
Notes
If T is a reference type then is_const::value is always false. The
proper way to check a potentially-reference type for const-ness is to
remove the reference: is_const::type>.
for(auto it=vec.begin(); it!=vec.end(); ++it) {
std::cout << std::is_const<std::remove_reference<decltype(*it)>::type>::value << std::endl;
}
for(auto it=vec.cbegin(); it!=vec.cend(); ++it) {
std::cout << std::is_const<std::remove_reference<decltype(*it)>::type>::value << std::endl;
}

Should I return gsl::span<const T> instead of const std::vector<T>&

I have a class with a std::vector<int> member and a member function returning a const reference to that vector.
class demo {
public:
//...
const std::vector<int> & test() const {
return iv;
}
private:
std::vector<int> iv;
};
I plan to change the member type to a different array like container type with just enough functionality and a smaller memory footprint (e.g. std::experimental::dynarray, std::unique_ptr<int[]>). Therefore I thought it would be a good idea to not return the real container as a const reference but to return a view to the elements as a gsl::span<const int>.
class demo {
public:
//...
gsl::span<const int> test() const {
return iv;
}
private:
std::vector<int> iv;
};
But this breaks code that worked with the const vector<int>& because two span instances of the same unmodified vector can't be used to iterate over the elements:
demo d;
std::cout << (d.test().begin() == d.test().begin()) << "\n";
std::cout << (d.test().end() == d.test().end()) << "\n";
for( auto it = d.test().begin(), end = d.test().end(); it != end; ++it )
std::cout << *it << "\n";
This prints 0 0 and then crashes because the test it != end never fails.
A range based for loop works of course, but this loop is valid and therefore must also work as expected.
I had expected, that all spans from the same range of the same container are equal so that iterators of any of these spans are comparable (container not modified of course). Certainly there is a good reason why this isn't so.
So my question is, what is the best way to return such a view to elements of a array like container whose type should not be visible to the caller.
You use iterator of temporary, so your iterator become invalided directly after affectation.
You may use the following:
auto&& view = d.test();
for (auto it = view.begin(), end = view.end(); it != end; ++it) {
std::cout << *it << "\n";
}
So the answer, is NO, because it breaks code, that was valid before.

temporary object in range-based for

I know that in general the life time of a temporary in a range-based for loop is extended to the whole loop (I've read C++11: The range-based for statement: "range-init" lifetime?). Therefore doing stuff like this is generally OK:
for (auto &thingy : func_that_returns_eg_a_vector())
std::cout << thingy;
Now I'm stumbling about memory issues when I try to do something I thought to be similar with Qt's QList container:
#include <iostream>
#include <QList>
int main() {
for (auto i : QList<int>{} << 1 << 2 << 3)
std::cout << i << std::endl;
return 0;
}
The problem here is that valgrind shows invalid memory access somewhere inside the QList class. However, modifying the example so that the list is stored in variable provides a correct result:
#include <iostream>
#include <QList>
int main() {
auto things = QList<int>{} << 1 << 2 << 3;
for (auto i : things)
std::cout << i << std::endl;
return 0;
}
Now my question is: am I doing something dumb in the first case resulting in e.g. undefined behaviour (I don't have enough experience reading the C++ standard in order to answer this for myself)? Or is this an issue with how I use QList, or how QList is implemented?
Since you're using C++11, you could use initialization list instead. This will pass valgrind:
int main() {
for (auto i : QList<int>{1, 2, 3})
std::cout << i << std::endl;
return 0;
}
The problem is not totally related to range-based for or even C++11. The following code demonstrates the same problem:
QList<int>& things = QList<int>() << 1;
things.end();
or:
#include <iostream>
struct S {
int* x;
S() { x = NULL; }
~S() { delete x; }
S& foo(int y) {
x = new int(y);
return *this;
}
};
int main() {
S& things = S().foo(2);
std::cout << *things.x << std::endl;
return 0;
}
The invalid read is because the temporary object from the expression S() (or QList<int>{}) is destructed after the declaration (following C++03 and C++11 ยง12.2/5), because the compiler has no idea that the method foo() (or operator<<) will return that temporary object. So you are now refering to content of freed memory.
The compiler can't possibly know that the reference that is the result of three calls to operator << is bound to the temporary object QList<int>{}, so the life of the temporary is not extended. The compiler does not know (and can't be expected to know) anything about the return value of a function, except its type. If it's a reference, it doesn't know what it may bind to. I'm pretty sure that, in order for the life-extending rule to apply, the binding has to be direct.
This should work because the list is no longer a temporary:
#include <iostream>
#include <QList>
int main() {
auto things = QList<int>{};
for (auto i : things << 1 << 2 << 3)
std::cout << i << std::endl;
return 0;
}
And this should work because the binding is direct, so the rule can apply:
#include <iostream>
#include <QList>
int main() {
for (auto i : QList<int>{1, 2, 3})
std::cout << i << std::endl;
return 0;
}