Skip to the content of the web site.

Lesson 224: Why does a = b = c; work?

Previous lesson Next lesson


Up to this point, we have always restricted assignments to individual statements; however, you may at some point come across C++ code that looks like this:

	a = b = 0;

If you step through such code with the debugger, you will note that this assigns both a and b the value of 0; however, how does this work?

Recall that while usually we see Boolean-valued operators and logical operators in the context of a conditional statement; for example

	if ( (n < 0) || (n >= capacity) ) {
		// Do something
	}

it is never-the-less possible to simply evaluate a Boolean-valued or logical operator and either assign that result to a variable or use it as a return value from a function:

	bool is_valid_index( int m, int n, size_t M, size_t N ) {
		bool is_valid{(m >= 0) && (m < M)};
		is_valid = is_valid && (n >= 0);
		return is_valid && (n < N);
	}

Now, this is a concocted example, but never-the-less, it demonstrates valid C++ code where Boolean-valued and logical operators are used in the contexts of initialization, assignment and return statements.

The = operator also has a return value, namely, the value that is assigned, so you could, for example:

#include <iostream>

int main();
void f( int value );

void f( int value ) {
	std::cout << value << std::endl;
}

int main() {
	int i{42};

	std::cout << (i = 91) << std::endl;
	f( i = 247 );

	return 0;
}

Additionally, you can therefore correctly deduce that the following would work as expected:

	int i, j;

	i = (j = 42);

which would assign j the value of $42$ while returning this value, which is then assigned to i.

This leaves us with the rather enigmatic

	int i, j;

	i = j = 42;

You may recall that the integers are associative under addition and multiplication, so the following are all identical:

$a + b + c = (a + b) + c = a + (b + c)$

and

$abc = (ab)c = a(bc)$.

Subtraction and division are not associative, so the following will usually give different results:

$(a - b) - c \ne a - (b - c)$

except when $c = 0$ and

$(a \divide b) \divide c \ne a \divide (b \divide c)$

except when $a = 0$ or $c = \pm 1$.

Thus, unless we have a convention for reading $a - b - c$ and $a \divide b \divide c$, the statement is ambiguous. The convention is to calculate the operations left-to-right, so because we determine the result of the left operation first, we say that subtraction and division are left associative.

In C++, if we were to take the statement i = j = 42;and interpret it using left associativity, the result would be nonsensical, for if we had:

	int i, j;

	(i = j) = 42;

thus, i would be assigned whatever default value j was given (let us say 0, although under certain circumstances, it could be any other number), and then we would be left with

	int i, j;

	(0) = 42;

and you cannot assign to a literal. The only reasonable interpretation of i = j = 42; is to use right associativity, whereby the statement is automatically evaluated from right to left.

The following eleven binary operators are right associative:

	=
	+=	-=	*=	/=	%=
	<<=	>>=	&=	^=	|=
#include <iostream>

int main();

int main() {
	int i{1}, j{2}, k{3};

	i = j += k *= 3;
	std::cout << i << ", " << j << ", " << k << std::endl;
	i /= j -= k %= 14;
	std::cout << i << ", " << j << ", " << k << std::endl;

	return 0;
}

As these are all assignment operators, all but the last operand must be in some way assignable.

In general, however, there is no significant benefit to using any of these binary operators in a manner that requires associativity to ever come into play.


Previous lesson Next lesson