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;
}
Related
I'm trying to initialize a tuple (using std::make_tuple), and I want to store an std::type_info in it as well, but I don't know why for some reason this simple looking piece of code doesn't compile.
I tried running the following code:-
#include <iostream>
#include <string>
#include <tuple>
#include <typeinfo>
#include <any>
int main()
{
std::tuple<std::type_info, int, std::any> tpl = std::make_tuple(typeid(float), 1, "Hello");
const auto&[type, num, anyval] = tpl;
std::cout<<type.name()<<std::endl;
std::cout<<num<<std::endl;
std::cout<<std::any_cast<const char*>(anyval)<<std::endl;
return 0;
}
If I only omit storing the first value (ie. type info) and create only std::tuple<int, std::any> there doesn't seem to be any problem, but this attempt fails. How to get around it, as I've to store the std::type_info object anyhow.
Problem is that std::type_info do not have ANY constructors. No copy, no move or default constructors. So you can't copy this value. You can get only reference of it.
You can use std::reference_wrapper as a workarund:
int main()
{
std::tuple<std::reference_wrapper<const std::type_info>, int, std::any> tpl = std::make_tuple(std::cref(typeid(float)), 1, "Hello");
const auto& [type, num, anyval] = tpl;
std::cout << type.get().name() << std::endl;
std::cout << num << std::endl;
std::cout << std::any_cast<const char*>(anyval) << std::endl;
return 0;
}
https://godbolt.org/z/czobc3h57
As point out by StoryTeller there is std::type_index to address this issue in nicer way:
int main()
{
std::tuple<std::type_index, int, std::any> tpl = std::make_tuple(std::type_index(typeid(float)), 1, "Hello");
const auto& [type, num, anyval] = tpl;
std::cout << type.name() << std::endl;
std::cout << num << std::endl;
std::cout << std::any_cast<const char*>(anyval) << std::endl;
return 0;
}
https://godbolt.org/z/z5dv8Wf86
I overlooked this, but now I've a solution which might be even better.
I skipped storing std::type_info (or std::type_index) at all. The third type (std::any) contains an std::type_info for the type it is enclosing, so storing it twice wouldn't be a good thing anyways.
Although the standard library does provide std::type_index which I didn't knew but came across while researching for this a bit.
#include <iostream>
#include <type_traits>
using namespace std;
template<typename T>
void f(T&&)
{
cout << boolalpha << std::is_const_v<T> << endl;
cout << boolalpha << std::is_const_v<T&&> << endl;
}
int main()
{
const int n = 1;
f(n);
}
The output is:
false
false
Here, n is an obvious const variable, why does std::is_const_v not behave as expected?
std::is_const is false when the type is a reference:
cppreference:
If T is a reference type then is_const<T>::value is always false. The proper way to check a potentially-reference type for const-ness is to remove the reference: is_const<typename remove_reference<T>::type>
In your specific case, T is a forwarding reference, which will be deduced as lvalue reference when passing a lvalue argument. That's why you see the false in two cases.
On this thread (Should I return an rvalue reference parameter by rvalue reference?), it is written that The parameter cannot be a temporary (which is just what rvalue references represent).
If I understand this sentence well, this code should not be correct
#include <string>
#include <iostream>
std::string &&f(std::string &&a) {
return std::move(a);
}
int main() {
std::string a = f("lol");
std::cout << a << std::endl;
return 0;
}
However, when we look to std::get, it seems that it returns rvalue reference for temporary tuple, and I actually think that there is no issue with using std::get with temporary tuple.
So, I think I misunderstand the sentence above, and the prior code is correct. However, this one is not correct :
#include <string>
#include <iostream>
std::string &&f(std::string &&a) {
return std::move(a);
}
int main() {
std::string &&a = f("lol");
std::cout << a << std::endl;
return 0;
}
It would be correct if the function returns a value and not rvalue reference. Am I correct?
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/
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;
}