34

when I do this (in my class)

public:
    Entity()
    {
        re_sprite_eyes = new sf::Sprite();
        re_sprite_hair = new sf::Sprite();
        re_sprite_body = new sf::Sprite();
    }

private:
    sf::Sprite* re_sprite_hair;
    sf::Sprite* re_sprite_body;
    sf::Sprite* re_sprite_eyes;

Everything works fine. However, if I change the declarations to this:

private:
    sf::Sprite* re_sprite_hair, re_sprite_body, re_sprite_eyes;

I get this compiler error:

error: no match for 'operator=' in '((Entity*)this)->Entity::re_sprite_eyes = (operator new(272u), (<statement>, ((sf::Sprite*)<anonymous>)))

And then it says candidates for re_sprite_eyes are sf::Sprite objects and/or references.

Why does this not work? Aren't the declarations the same?

nvoigt
  • 75,013
  • 26
  • 93
  • 142
  • 12
    You have discovered an interesting property of C/C++ declarations: the asterisk belongs to the variable, not to the type. One way to constantly remind yourself of this is to put space after the type and before the asterisk. – Sergey Kalinichenko Nov 29 '12 at 03:23

5 Answers5

71

sf::Sprite* re_sprite_hair, re_sprite_body, re_sprite_eyes;

Does not declare 3 pointers - it is one pointer and 2 objects.

sf::Sprite* unfortunately does not apply to all the variables declared following it, just the first. It is equivalent to

sf::Sprite* re_sprite_hair;
sf::Sprite re_sprite_body;
sf::Sprite re_sprite_eyes;

You want to do:

sf::Sprite *re_sprite_hair, *re_sprite_body, *re_sprite_eyes;

You need to put one star for each variable. In such cases I prefer to keep the star on the variable's side, rather than the type, to make exactly this situation clear.

Karthik T
  • 31,456
  • 5
  • 68
  • 87
39

In both C and C++, the * binds to the declarator, not the type specifier. In both languages, declarations are based on the types of expressions, not objects.

For example, suppose you have a pointer to an int named p, and you want to access the int value that p points to; you do so by dereferencing the pointer with the unary * operator, like so:

x = *p;

The type of the expression *p is int; thus, the declaration of p is

int *p;

This is true no matter how many pointers you declare within the same declaration statement; if q and r also need to be declared as pointers, then they also need to have the unary * as part of the declarator:

int *p, *q, *r;

because the expressions *q and *r have type int. It's an accident of C and C++ syntax that you can write T *p, T* p, or T * p; all of those declarations will be interpreted as T (*p).

This is why I'm not fond of the C++ style of declaring pointer and reference types as

T* p;
T& r;

because it implies an incorrect view of how C and C++ declaration syntax works, leading to the exact kind of confusion that you just experienced. However, I've written enough C++ to realize that there are times when that style does make the intent of the code clearer, especially when defining container types.

But it's still wrong.


This is a (two years late) response to Lightness Races in Orbit (and anyone else who objects to my labeling the T* p convention as "wrong")...

First of all, you have the legion of questions just like this one that arise specifically from the use of the T* p convention, and how it doesn't work like people expect. How many questions on this site are on the order of "why doesn't T* p, q declare both p and q as pointers?"

It introduces confusion - that by itself should be enough to discourage its use.

But beyond that, it's inconsistent. You can't separate array-ness or function-ness from the declarator, why should you separate pointer-ness from it?

"Well, that's because [] and () are postfix operators, while * is unary". Yes, it is, so why aren't you associating the operator with its operand? In the declaration T* p, T is not the operand of *, so why are we writing the declaration as though it is?

If a is "an array of pointers", why should we write T* a[N]? If f is "a function returning a pointer", why should we write T* f()? The declarator system makes more sense and is internally consistent if you write those declarations as T *a[N] and T *f(). That should be obvious from the fact that I can use T as a stand-in for any type (indeed, for any sequence of declaration specifiers).

And then you have pointers to arrays and pointers to functions, where the * must be explicitly bound to the declarator1:

T (*a)[N];
T (*f)();

Yes, pointer-ness is an important property of the thing you're declaring, but so are array-ness and function-ness, and emphasizing one over the other creates more problems than it solves. Again, as this question shows, the T* p convention introduces confusion.

Because * is unary and a separate token on its own you can write T* p, T *p, T*p, and T * p and they'll all be accepted by the compiler, but they will all be interpreted as T (*p). More importantly, T* p, q, r will be interpreted as T (*p), q, r. That interpretation is more obvious if you write T *p, q, r. Yeah, yeah, yeah, "declare only one thing per line and it won't be a problem." You know how else to not make it a problem? Write your declarators properly. The declarator system itself will make more sense and you will be less likely to make mistake.

We're not arguing over an "antique oddity" of the language, it's a fundamental component of the language grammar and its philosophy. Pointer-ness is a property of the declarator, just like array-ness and function-ness, and pretending it's somehow not just leads to confusion and makes both C and C++ harder to understand than they need to be.

I would argue that making the dereference operator unary as opposed to postfix was a mistake2, but that's how it worked in B, and Ritchie wanted to keep as much of B as possible. I will also argue that Bjarne's promotion of the T* p convention is a mistake.


  1. At this point in the discussion, somebody will suggest using a typedef like
    typedef T arrtype[N]; 
    arrtype* p;
    which just totally misses the point and earns the suggester a beating with the first edition of "C: The Complete Reference" because it's big and heavy and no good for anything else.
  2. Writing T a*[N]*() as opposed to T (*(*a)[N])() is definitely less eye-stabby and scans much more easily.
John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Is is wrong for "T& r;"? One cannot write T t = &r; I write "T *p;" and "T& r;" – Jive Dadson Nov 29 '12 at 04:18
  • 2
    The use of & to indicate a reference is a C++ construct that does not sit well with the original C declarations. Back in the day, I had reservations about the way references entered into C++, partly because of that. The address-of operator is overloaded in a confusing way - not as badly as << and >> tough. :-) – Jive Dadson Nov 29 '12 at 04:26
  • 1
    @JiveDadson: As far as the syntax is concerned, `T& r` is "wrong" (it's interpreted as `T (&r)`, so multiple declarations would have to be written `T &r, &s, &q`). I understand the point you're making (`&x` has type `T *`, not `T`), and yes, overloading `&` this way does cause some heartburn. – John Bode Nov 29 '12 at 04:52
  • Except in this one specific case (which you shouldn't be doing anyway) (and, okay, some archanely-written complex types, if you're so inclined) it couldn't be of any less consequence, so the C++ style is _far_ superior overall. It's not "wrong". Right-aligning your symbols just to pander to an antique oddity of the language's internals is one of the biggest abstraction leaks. – Lightness Races in Orbit Sep 05 '18 at 17:38
  • I disagree both with the premise that the "C++ style is wrong" and the premise that the "C++ style is superior". The C++ style works well in some contexts (e.g. when declaring exactly one variable per declaration) and misleads programmers in others (e.g. implying that `Foo* a, b` declares two pointers rather than one). An alternative (with pros and cons, both technical and religion based) is to use a `typedef` or (since C++) a type alias and avoid a need to argue over the position of `*` or `&` in variable/argument declarations while easing code maintenance when changing the types. – Peter Oct 08 '21 at 02:19
  • 1
    @Peter: Perhaps not surprisingly, I also have very strong opinions about hiding pointer-ness behind a `typedef`; unless you're willing to build an API to completely abstract out all pointer operations as well, then don't bother. If I have to know that `p` is a pointer in order to use it properly (e.g., I have to dereference it directly), then don't hide that information behind a `typedef` or other type alias. Make it explicit in the declaration. I'd rather deal with `T* p;` than `typedef-name-that-doesn't-indicate-pointerness-at-all p;` – John Bode May 17 '22 at 15:06
  • @JohnBode My previous comment was not concerned with "hiding pointerness". My point was that something like `FooPtr a,b` is clear in `a` and `b` have the same type, but `Foo* a, b` can be more easily misinterpreted as meaning `a` and `b` are both the same type. But, yeah, it is a good idea to ensure that the `typedef` (or type alias) name is informative e.g. it is misleading for a programmer to name a pointer to a `Foo` something like `Dog` rather than `FooPtr` (assuming, of course, it is reasonable to assume our programmer understands that `FooPtr` represents a pointer to `Foo`). – Peter May 17 '22 at 15:37
5

In C++11 you have a nice little workaround, which might be better than shifting spaces back and forth:

template<typename T> using type=T;
template<typename T> using func=T*;

// I don't like this style, but type<int*> i, j; works ok
type<int*> i = new int{3},
           j = new int{4};

// But this one, imho, is much more readable than int(*f)(int, int) = ...
func<int(int, int)> f = [](int x, int y){return x + y;},
                    g = [](int x, int y){return x - y;};
rici
  • 234,347
  • 28
  • 237
  • 341
4

Another thing that may call your attention is the line:

int * p1, * p2;

This declares the two pointers used in the previous example. But notice that there is an asterisk (*) for each pointer, in order for both to have type int* (pointer to int). This is required due to the precedence rules. Note that if, instead, the code was:

int * p1, p2;

p1 would indeed be of type int*, but p2 would be of type int. Spaces do not matter at all for this purpose. But anyway, simply remembering to put one asterisk per pointer is enough for most pointer users interested in declaring multiple pointers per statement. Or even better: use a different statemet for each variable.

From http://www.cplusplus.com/doc/tutorial/pointers/

nhahtdh
  • 55,989
  • 15
  • 126
  • 162
Robin Nag
  • 41
  • 3
1

The asterisk binds to the pointer-variable name. The way to remember this is to notice that in C/C++, declarations mimic usage.

The pointers might be used like this:

sf::Sprite *re_sprite_body;
// ...
sf::Sprite sprite_bod = *re_sprite_body;

Similarly,

char *foo[3];
// ...
char fooch = *foo[1];

In both cases, there is an underlying type-specifier, and the operator or operators required to "get to" an object of that type in an expression.

Jive Dadson
  • 16,680
  • 9
  • 52
  • 65