Difference between const auto & and auto & if object of reference is const - c++

// case 1
const int i = 42;
const auto &k = i;
// case 2
const int i = 42;
auto &k = i;
Do we need the const keyword before auto in this scenario? After all, a reference (k) to an auto-deduced type will include the top level const of the object (const int i). So I believe k will be a reference to an integer that is constant (const int &k) in both cases.
If that is true, does that mean that const auto &k = i; in case 1 is replaced by the compiler as just const int &k = i; (auto being replaced with int)? Whereas in case 2, auto is replaced with const int?

auto keyword automatically decides the type of the variable at compile time.
In your first case, auto is reduced to int where it's reduced to const int in the second case. So, both of your cases are reduced to the same code as:
const int &k = i;
However, it's better to have the const explicitly for better readability and to make sure your variable TRULY is const.

Type deduction with auto works like template argument type deduction with a few exceptions that don't apply in the given example. Hence
const int i = 42;
const auto& k1 = i; // same as const int& k1 = i;
auto& k2 = i; // same as (const int)& k2 = i;
It is probably more readable to add the const qualifier nevertheless.
Here is another example where favoring brevity with auto is misleading:
int *i;
const auto k1 = i; // same as int* const k1 = i;
const auto *k2 = i; // same as const int *k2 = i;
In the first case, the object that i points to can be modified through k1, in the second case, it can't.

Hi and welcome to stack overflow.
As this little test program shows, no matter how you specify the type of k, the compiler will never let you lose the constness of i.
#include <iostream>
#include <type_traits>
#include <string>
#define XSTR(s) STR(s)
#define STR(s) #s
template<class T>
struct type;
template<>
struct type<int>
{
static std::string name() { return "int"; }
};
template<class T>
struct type<T&&>
{
static std::string name() { return type<T>::name() + " &&"; }
};
template<class T>
struct type<T&>
{
static std::string name() { return type<T>::name() + " &"; }
};
template<class T>
struct type<T const>
{
static std::string name() { return type<T>::name() + " const"; }
};
#define REPORT_CONST(decl, var, assign) \
{ \
decl var = assign; \
do_it(STR(decl var = assign;), var); \
}
template<class Var>
void do_it(const char* message, Var&&)
{
std::cout << "case: " << message << " results in type: " << type<Var>::name() << '\n';
}
int main()
{
const int i = 42;
REPORT_CONST(const auto &, k, i);
REPORT_CONST(auto &, k, i);
REPORT_CONST(auto &&, k, std::move(i));
REPORT_CONST(auto const &&, k, std::move(i));
REPORT_CONST(int const&, k, i);
// REPORT_CONST(int &, k, i); // error: binding reference of type 'int&' to 'const int' discards qualifiers
}
Expected results:
case: const auto & k = i; results in type: int const &
case: auto & k = i; results in type: int const &
case: auto && k = std::move(i); results in type: int const &
case: auto const && k = std::move(i); results in type: int const &
case: int const& k = i; results in type: int const &
http://coliru.stacked-crooked.com/a/7c72c8ebcf42c351
Note also the decay of named r-values to l-values.

There is a slight difference in first case auto will be deduced to const int and in the second case to int (as you explcitly stated the const).
cpp-reference states
The keyword auto may be accompanied by modifiers, such as const or &, which will participate in the type deduction. For example, given const auto& i = expr;, the type of i is exactly the type of the argument u in an imaginary template template void f(const U& u) if the function call f(expr) was compiled. Therefore, auto&& may be deduced either as an lvalue reference or rvalue reference according to the initializer, which is used in range-based for loop.
So for you this means
// case 1
const int i = 42;
const auto &k = i; // auto -> int
// case 2
const int i = 42;
auto &k = i; // auto -> const int
More important in my opinion is, however, that the intent is stated more clearly if you state const explcitly and the guarantees the constness. Therefore in this case I would clearly prefer it.

The accepted answer is correct, i.e. there is no difference in regards to the compiled result. What's important to note is that the auto& version is coupling the const-ness of the k reference with the const-ness of the variable i. I figured since the question is titled 'Difference between const auto & and auto & ...' then it's important to emphasize the pragmatic difference here, in which if you don't put a the const keyword, you cannot guarantee the reference will have this cv-qualification. In some scenarios where this is desired, why leave it to chance that i will remain const in the future?

Related

Template argument deduction doesn't work the same as in structured binding

I formally been told(some books or in web) that the type deduction in auto is the same as argument type deduction in template.
However, codes below doesn't compile successfully because it's raises increment of read-only reference a error. Though the template version deduct T as type int instead of const int &. Why these two cases have different deduction results?
#include "bits/stdc++.h"
using namespace std;
template <typename T>
T minmax2(T n1, T n2) {
n1++;
return n2;
}
int main() {
auto [a, b] = minmax(1, 2); // minmax() return const T &
a++; // compile error: can not alter read-only varaible.
const int & c = 1, & d = 2;
minmax2(c, d); // the same parameter type, don't raise error
}
Another example:
int tmp = 1;
const int & test() {
return tmp;
}
template <typename T>
void test1(T n) {
n++; // no error
}
int main() {
auto a = test(); // If the satement is true, I expect this is the same as test1.
a++; // no compile error, which is different with the previous code exp
const int & b = 2;
test1(b);
}
I think your confusion comes from the fact that structured bindings behave a bit differently than the ordinary auto, at least from the user's perspective. Consider the latter first:
const int& i = 0;
auto j = i;
std::cout << &i << std::endl;
std::cout << &j << std::endl; // different address
j++; // compiles
Here, i and j refer to different objects and the type of j is int (not const int or const int&). This exactly corresponds with the template argument deduction when the argument is passed by value.
But with structured bindings, the situation is different:
int c = 0, d = 0;
auto [a, b] = std::pair<const int&, const int&>(c, d);
std::cout << &a << std::endl;
std::cout << &c << std::endl; // same address
// a++; // would not compile
Here, the type of a and b is const int& and both a and c refer to the same object.
Live demo: https://godbolt.org/z/Wq1zsz
Actually, structured bindings also ignores const and reference parts of an initialization expression, but at an "outer" level, that is, at a level of std::pair in our case:
const auto& p = std::make_pair(c, d);
auto [x, y] = p;
std::cout << &p.first << std::endl;
std::cout << &x << std::endl; // different address
Here, x is a new object with type int. If we used const auto& [x, y] = p; instead, then x would refer to p.first and its type would be const int&.
Your comment already states your problem: // minmax() return const T &
cppreference for std::minmax
After your binding, you essentially have the following variables: const int &a and const int &b, afterwards you're trying to increment your const reference of a which is obviously not working.
To fix your code, you can simply create a copy of a like this:
auto [a, b] = minmax(1, 2); // minmax() return const T &
auto a_cpy = a;
++a_cpy;
The signature of std::minmax is
template< class T >
std::pair<const T&,const T&> minmax( const T& a, const T& b );
The call
auto [a, b] = minmax(1, 2);
has equivalent behavior to
std::pair<const int&,const int&> temp = minmax(1, 2);
const int &a = temp.first;
const int &b = temp.second;
That is how structured binding works.
As you can see the type T is deduced as int. std::minmax adds a const and a & to the type and makes a pair of it.
You could write your own minmax2 as
template <typename T>
std::pair<T,T> minmax2(T n1, T n2) {
return std::make_pair(n1, n2);
}
The call
auto [a, b] = minmax2(1, 2);
has equivalent behavior to
std::pair<int, int> temp = minmax2(1, 2);
int &a = temp.first;
int &b = temp.second;
Here you can
++a;

Why auto drops the reference in return value? [duplicate]

Please take a look at the following simple code:
class Foo
{
public:
Foo(){}
~Foo(){}
Foo(const Foo&){}
Foo& operator=(const Foo&) { return *this; }
};
static Foo g_temp;
const Foo& GetFoo() { return g_temp; }
I tried to use auto like this:
auto my_foo = GetFoo();
I expected that my_foo will be a constant reference to Foo, which is the return type of the function. However, the type of auto is Foo, not the reference. Furthermore, my_foo is created by copying g_temp. This behavior isn't that obvious to me.
In order to get the reference to Foo, I needed to write like this:
const auto& my_foo2 = GetFoo();
auto& my_foo3 = GetFoo();
Question: Why does auto deduce the return type of GetFoo as an object, not a reference?
Read this article: Appearing and Disappearing consts in C++
Type deduction for auto variables in C++0x is essentially the same as
for template parameters. (As far as I know, the only difference
between the two is that the type of auto variables may be deduced from
initializer lists, while the types of template parameters may not be.)
Each of the following declarations therefore declare variables of type
int (never const int):
auto a1 = i;
auto a2 = ci;
auto a3 = *pci;
auto a4 = pcs->i;
During type deduction for template parameters and auto variables, only
top-level consts are removed. Given a function template taking a
pointer or reference parameter, the constness of whatever is pointed
or referred to is retained:
template<typename T>
void f(T& p);
int i;
const int ci = 0;
const int *pci = &i;
f(i); // as before, calls f<int>, i.e., T is int
f(ci); // now calls f<const int>, i.e., T is const int
f(*pci); // also calls f<const int>, i.e., T is const int
This behavior is old news, applying as it does to both C++98 and
C++03. The corresponding behavior for auto variables is, of course,
new to C++0x:
auto& a1 = i; // a1 is of type int&
auto& a2 = ci; // a2 is of type const int&
auto& a3 = *pci; // a3 is also of type const int&
auto& a4 = pcs->i; // a4 is of type const int&, too
Since you can retain the cv-qualifier if the type is a reference or pointer, you can do:
auto& my_foo2 = GetFoo();
Instead of having to specify it as const (same goes for volatile).
Edit: As for why auto deduces the return type of GetFoo() as a value instead of a reference (which was your main question, sorry), consider this:
const Foo my_foo = GetFoo();
The above will create a copy, since my_foo is a value. If auto were to return an lvalue reference, the above wouldn't be possible.
You can take simple answer as granted by MSVC Technical documentation:
Using auto drops references, const qualifiers, and volatile
qualifiers.
You don't need a function to achieve similar results. Consider:
int var;
const int & cref = var;
auto avar = cref;
The type of avar will be int not const int & nor int &.
To go a bit deeper into this we can go to cppreference.com and refer to template argument deduction analogy.
For example, given const auto& i = expr;, the type of i is exactly the type of the argument u in an imaginary template template<class U> void f(const U& u).
We tend to intuitively use templates and until it works we never really bother about template argument deduction rules. These rules are in fact quite complex. But we can make the "imaginary template for auto" non-imaginary and test what would have happened if we provided auto i = expr; to the template. In our case the template may look like this:
template<class U>
void f(U u)
{
cout << type_name<decltype(u)>() << "\n";
}
Let's feed it with variables with types of our interest:
int i = 0;
int & ir = i;
const int & cir = i;
f(i);
f(ir);
f(cir);
And the what's at the output?
int
int
int
The same thing would have happened with auto as predicted by MSVC docs :)
Here's the code if you wanted to play.
Here's also example from MS website if it disappeared one day
// cl.exe /analyze /EHsc /W4
#include <iostream>
using namespace std;
int main( )
{
int count = 10;
int& countRef = count;
auto myAuto = countRef;
countRef = 11;
cout << count << " ";
myAuto = 12;
cout << count << endl;
}
In the previous example, myAuto is an int, not an int reference, so the output is 11 11, not 11 12 as would be the case if the reference qualifier hadn't been dropped by auto

C++: Confusion on auto deduction using a const against an array

Example code:
int aa[] = {1,2,3,4};
auto const p = aa;
Why p deduced as int *const instead int const *?
Because aa is a non-const array, it will decay to pointer-to-non-const and therefore p will be deduced to be a pointer-to-non-const. And because you declared p const, it will be a const pointer-to-non-const.
Declaring a variable const will make that variable const. It doesn't make the type of the variable different (pointer-to-const and pointer-to-non-const are different types) except in the sense that it may be a const qualified version of that same type.
I think there is little more than that, when You wrote:
auto const p = aa;
it means:
auto const p = &aa[0];
so on the right hand side we have pointer to int (not const pointer to int!), so following auto type deduction rules it evaluates to template like this:
template<typename T>
void func_for_ptr(const T* param);
So in our case parameterType is const T*
but T itself is pointer to int.
Constness of type is not changed!
The same effect can be observed other way around, try to define template:
template<typename T>
void foo(const T param) {
int tmp = 0;
T val = &tmp;
*val = 0;
}
int main() {
int *x = new int(1);
foo(x);
}
T was not deduced to pointer to const so You r able to override ptr, but if You will write:
template<typename T>
void foo(T param) {
int tmp = 0;
T val = &tmp;
*val = 0;
}
int main() {
const int *x = new int(1);
foo(x);
}
Type T will be deduced to pointer to const and you will get error. In shortcut auto is always like parameter type.

C++11 auto: what if it gets a constant reference?

Please take a look at the following simple code:
class Foo
{
public:
Foo(){}
~Foo(){}
Foo(const Foo&){}
Foo& operator=(const Foo&) { return *this; }
};
static Foo g_temp;
const Foo& GetFoo() { return g_temp; }
I tried to use auto like this:
auto my_foo = GetFoo();
I expected that my_foo will be a constant reference to Foo, which is the return type of the function. However, the type of auto is Foo, not the reference. Furthermore, my_foo is created by copying g_temp. This behavior isn't that obvious to me.
In order to get the reference to Foo, I needed to write like this:
const auto& my_foo2 = GetFoo();
auto& my_foo3 = GetFoo();
Question: Why does auto deduce the return type of GetFoo as an object, not a reference?
Read this article: Appearing and Disappearing consts in C++
Type deduction for auto variables in C++0x is essentially the same as
for template parameters. (As far as I know, the only difference
between the two is that the type of auto variables may be deduced from
initializer lists, while the types of template parameters may not be.)
Each of the following declarations therefore declare variables of type
int (never const int):
auto a1 = i;
auto a2 = ci;
auto a3 = *pci;
auto a4 = pcs->i;
During type deduction for template parameters and auto variables, only
top-level consts are removed. Given a function template taking a
pointer or reference parameter, the constness of whatever is pointed
or referred to is retained:
template<typename T>
void f(T& p);
int i;
const int ci = 0;
const int *pci = &i;
f(i); // as before, calls f<int>, i.e., T is int
f(ci); // now calls f<const int>, i.e., T is const int
f(*pci); // also calls f<const int>, i.e., T is const int
This behavior is old news, applying as it does to both C++98 and
C++03. The corresponding behavior for auto variables is, of course,
new to C++0x:
auto& a1 = i; // a1 is of type int&
auto& a2 = ci; // a2 is of type const int&
auto& a3 = *pci; // a3 is also of type const int&
auto& a4 = pcs->i; // a4 is of type const int&, too
Since you can retain the cv-qualifier if the type is a reference or pointer, you can do:
auto& my_foo2 = GetFoo();
Instead of having to specify it as const (same goes for volatile).
Edit: As for why auto deduces the return type of GetFoo() as a value instead of a reference (which was your main question, sorry), consider this:
const Foo my_foo = GetFoo();
The above will create a copy, since my_foo is a value. If auto were to return an lvalue reference, the above wouldn't be possible.
You can take simple answer as granted by MSVC Technical documentation:
Using auto drops references, const qualifiers, and volatile
qualifiers.
You don't need a function to achieve similar results. Consider:
int var;
const int & cref = var;
auto avar = cref;
The type of avar will be int not const int & nor int &.
To go a bit deeper into this we can go to cppreference.com and refer to template argument deduction analogy.
For example, given const auto& i = expr;, the type of i is exactly the type of the argument u in an imaginary template template<class U> void f(const U& u).
We tend to intuitively use templates and until it works we never really bother about template argument deduction rules. These rules are in fact quite complex. But we can make the "imaginary template for auto" non-imaginary and test what would have happened if we provided auto i = expr; to the template. In our case the template may look like this:
template<class U>
void f(U u)
{
cout << type_name<decltype(u)>() << "\n";
}
Let's feed it with variables with types of our interest:
int i = 0;
int & ir = i;
const int & cir = i;
f(i);
f(ir);
f(cir);
And the what's at the output?
int
int
int
The same thing would have happened with auto as predicted by MSVC docs :)
Here's the code if you wanted to play.
Here's also example from MS website if it disappeared one day
// cl.exe /analyze /EHsc /W4
#include <iostream>
using namespace std;
int main( )
{
int count = 10;
int& countRef = count;
auto myAuto = countRef;
countRef = 11;
cout << count << " ";
myAuto = 12;
cout << count << endl;
}
In the previous example, myAuto is an int, not an int reference, so the output is 11 11, not 11 12 as would be the case if the reference qualifier hadn't been dropped by auto

Template Functions and Const/NonConst Reference Parameters

New to C++ and learning from books so I can be quite pedantic or miopic in my reasoning.
In the case of template functions, I have read that when a parameter is passed by Reference, only conversions from Reference / Pointer to NonConst to Reference / Pointer to Const are alllowed.
That means I believe that
template <typename T> int compare(T&, T&);
should fail when calling compare(ci1, ci1), with ci1 being consntant int, as conversions from Const to NonCost are not allowed for Reference parameters.
However it works in my compiler (Visual C++ 10). Can someone explain me what I get wrong?
template <typename T> int compare(T&, T&);
template <typename T> int compare(T &v1, T &v2)
{
// as before
cout << "compare(T, T)" << endl;
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}
const int ci1 = 10;
const int ci2 = 20;
int i1 = 10;
int i2 = 20;
compare(ci1, ci1);
compare(i1, i1);
The call
compare( ci1, ci1 );
yields T as type const int (in your preferred notation).
The effective function signature is then
int compare( int const&, int const& )
You can use typeid(x).name() the check out what types you actually have.
Note: with g++ that yields some ungrokkable short forms, which you then need to use a special g++-specific runtime lib function to decode.
Cheers & hth.
T will be whatever type the variable is - in your case const int, so the final instantiation of compare will look like
// T = const int
int compare(const int& v1, const int& v2)
in your first case with compare(ci1,ci2) and like
// T = int
int compare(int& v1, int& v2)
with compare(i1,i2).
In the first case, the template is instantiated with T = const int, which is fine.
You will get an error if you try compare(i1, ci1), since that will fail to find a template argument compatible with both int & and const int &. Changing the signature to compare(const T &, const T &) will fix that problem.
In the case of compare(ci1, ci1); T will be const int. This is why it works
This is acceptable because the function can be instantiated when you substitute int const for T.