We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Values, Types, Initialisation step 3 of 4
Braces refuse to lose your data
C++ has more ways to initialise a variable than any language needs. Two of them differ in a way that matters:
int a(3.9); // 3 — silently truncated
int b{3.9}; // error: narrowing conversion
Parentheses perform the conversion and say nothing. Braces refuse to compile. Same value, same target type, opposite outcome — and the second one is the behaviour you want almost everywhere.
What “narrowing” means
A conversion is narrowing when the destination type cannot represent every value of the source type. Braces reject narrowing conversions unless the value is a constant the compiler can prove fits:
int a{3.9}; // error — double to int, loses 0.9
int b{3.0}; // error — still double to int
int c{3}; // fine
char d{300}; // error — constant, and it does not fit
char e{65}; // fine — constant, and it does
double f{3}; // fine — int to double is not narrowing
Note the fourth line. Braces catch a constant that does not fit, which is a compile-time error for a bug that would otherwise be a runtime surprise.
Why this is a habit and not a rule
You will still see and write parentheses. std::vector<int> v(10) makes a
vector of ten zeros, and std::vector<int> v{10} makes a vector containing
the single element ten — that is the famous case where braces are the
surprising one, because std::initializer_list wins when it can.
So the practical guidance is narrower than “always use braces”:
Use braces when you are initialising a value from another value. Use parentheses when you are calling a constructor whose arguments are configuration rather than contents — sizes, capacities, counts.
The other reason braces are worth the habit
int x{}; // zero
double y{}; // 0.0
int* p{}; // nullptr
std::string s{}; // empty
An empty brace initialiser value-initialises: zero for arithmetic types, null
for pointers, the default constructor otherwise. int x; at block scope does
none of that — it leaves x holding whatever was there, and reading it is
undefined behaviour. That was mistake three in the previous problem.
Your task
struct Pixel {
std::uint8_t r;
std::uint8_t g;
std::uint8_t b;
};
Pixel scale(const Pixel& p, double factor);
Multiply each channel by factor and clamp the result to 0..255 before
storing it. std::uint8_t holds 0 to 255; a scaled channel can exceed that,
and letting it wrap turns a bright red pixel into a dark one.
The starter multiplies and assigns without clamping, using parentheses so that nothing complains. Convert the initialisation to braces and the compiler will tell you where you have not thought it through.
One more thing you will meet on the way. If you build the result as
Pixel{a, b, c}, clang-tidy will ask for
Pixel{.r = a, .g = b, .b = c} — designated initialisers, C++20. Six
characters, and they remove the class of bug where two fields of the same
type get swapped. For r, g and b that is a bug you would see on screen
and a test might not.
Stuck?
C++ reference solution
Sign in to attempt this problem and reveal the reference solution.