64

What's the difference between the types of bar1 and bar2?

int foo = 10;
auto bar1 = &foo;
auto *bar2 = &foo;

If both bar1 and bar2 are int*, does it makes sense to write the pointer declarator (*) in the bar2 declaration?

Henry Barker
  • 784
  • 1
  • 5
  • 9

6 Answers6

62

The declarations are exactly equivalent. auto works (almost) the same as template type deduction. Putting the star explicitly makes the code a bit easier to read, and makes the programmer aware that bar2 is a pointer.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
61

Using auto * "documents intention". And auto *p = expr; can be deduced correctly only if expr returns pointer. Example:

int f();

auto q = f(); // OK

auto *p = f(); // error: unable to deduce 'auto*' from 'f()'
Victor Dyachenko
  • 1,363
  • 8
  • 18
  • 5
    Yes, but next operator & is overloaded to return a notnull or const propagating smart pointer, and then auto * breaks. – Johan Lundberg Jan 17 '16 at 09:35
  • I agree with documenting intention, and some of the assumptions made in client code about what it should be doing with the returned types might be less valid if you stop using it as a pointer for example. So this type of writing can avoid bugs during refactoring. – yano Jul 07 '20 at 22:35
24

There is a big difference when you use const qualifiers:

int i;

// Const pointer to non-const int
const auto ip1 = &i; // int *const
++ip1; // error
*ip1 = 1; // OK

// Non-const pointer to const int
const auto* ip2 = &i; // int const*
++ip2; // OK
*ip2 = 1; // error
danpla
  • 655
  • 5
  • 9
  • This only depends on the placement of the `const` qualifier. If you explicitly want a constant pointer to non-constant memory, you can do: `auto* const ip3 = &i`. Then `ip3` and `ip1` behave exactly the same. – Kai Petzke Jun 28 '22 at 10:13
  • It helps when you read constness from right to left. `const auto *p` -> pointer to const auto. `const auto * const p` -> const pointer to const auto. `auto * const p` -> const pointer non const auto. I often even switch writing style to support that; in C++, `const int x` (x is an int which is const) is the same as `int const x` (x is a const x). – Sebastian Mach Jan 06 '23 at 07:45
17

In this specific example both bar1 and bar2 are the same. It's a matter of personal preference though I'd say that bar2 is easier to read.

However, this does not hold true for references as seen in this example:

#include <iostream>
using namespace std;

int main() {
    int k = 10;
    int& foo = k;
    auto bar = foo; //value of foo is copied and loses reference qualifier!
    bar = 5; //foo / k won't be 5
    cout << "bar : " << bar << " foo : " << foo << " k : " << k << endl;
    auto& ref = foo;
    ref = 5; // foo / k will be 5
    cout << "bar : " << bar << " foo : " << foo << " k : " << k;
    return 0;
}
Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
13

As others said, they'll generate the same code. The asterisk is line noise (and makes it harder to switch from raw pointers to smart pointers if, for example, &foo is ever replaced by get_foo()). If you want to be explicit, then by all means, be explicit; but when you're using type inference, just let the compiler do its job. Lack of asterisks does not imply that an object isn't a pointer.

Jeff Schwab
  • 613
  • 6
  • 16
  • 5
    I think the comment in parentheses deserves greater emphasis. Even in the simple examples others have given, adding the `*` may make the code more readable, but can make maintenance more difficult. I personally feel the primary benefit of `auto` is to ease maintenance, even more so than reducing typing or increasing readability. – ToddR Jan 04 '16 at 20:47
9

It doesn't matter as far as the interpretation of the C++ code goes; you can write whatever you want. However, there is a question of style and readability: Generally, you should not hide pointer, reference and CV qualifiers, and perhaps even smart pointers, in type aliases, since it makes it harder for the reader to understand that that's what's going on. Type aliases should package the semantically relevant type content, whereas qualifiers and modifiers should remain visible. So prefer the following:

 using Foo = long_namespace::Foobrigation<other_namespace::Thing>;
 using MyFn = const X * (int, int);

 std::unique_ptr<Foo> MakeThatThing(MyFn & fn, int x)   // or "MyFn * fn"
 { 
     const auto * p = fn(x, -x);
     return p ? p->Create() : nullptr;
 }

And don't say:

using PFoo = std::unique_ptr<Foo>;   // just spell it out
using MyFn = int(&)(int, int);       // unnecessary; & is easy to spell
auto p = fn(x, -x);                  // Don't know that p is a pointer

Note also that reference qualifiers (unlike pointers) genuinely change the type of the variable that's being declared, so they're not optional:

X & f();
auto a = f();    // copy!
auto & b = f();  // b is the same as the return value of f()

Finally, adding explicit const pointer qualifications can help const-correctness. Consider the next example, in which a container contains pointers-to-mutable, but we only require const access. Just auto * would deduce a pointer to mutable, which we can avoid by saying const explicitly:

std::vector<X*> v = /* ... */;
for (const auto * p : v)
{
    observe(p->foo());   // no need for a mutable *p
}
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084