Meaning, in a "normal" situation where the performance loss of type promotion is irrelevant, where nobody checks for equality on a float and where nobody declares anything funny like
void f(float x) {/*do something*/};
void f(double x) {/*do something completely different*/};
and so on.
I am aware that the general question of "C++ f suffix why?" has been asked a thousand times, but all the answers always are about technical details and contrived examples. I haven't found any satisfactory answer that shows an actually relevant situation, where the distinction is important.
I don't have a good "real-life" example at hand. Instead I want to try to convince you with a hopefully simple example.
Lets assume all function with overloads for float
and double
do "the right thing":
void f(float x) {/*do something*/};
void f(double x) {/*do the same thing*/};
Moreover, when only one of the overloads is available, we can call it with a literal of either type:
void f_float(float x) {/*do something*/};
void f_double(double x) {/*do the same thing*/};
f_float(3.0); // ok
f_float(3.0f); // ok
f_double(3.0); // ok
f_double(3.0f); // ok
float
s convert to double
s and vice versa.
If all code is fine with either float
or double
, this is end of story. However, you don't need to do something "funny" to get the code to break.
I haven't found any satisfactory answer that shows an actually relevant situation, where the distinction is important.
The distinction is important when the type of the literal is used to infer the type of something else. Being able to convert between double
and float
is not all you need to be able to replace any float
literal with a double
literal without breaking code.
In the above example f_float
can be called with either 3.0
or 3.0f
. Now suppose you have a function that only accepts a std::vector<float>
:
void bar(const std::vector<float>& x){}
and something that given a single value, constructs a vector:
template <typename T>
std::vector<T> make_vect(const T& t){ return {T{}}; }
Then the type of the literal makes the code either fail or pass:
bar(make_vect(0.3f));
//bar(x.make_vect(0.3)); // ERROR
TL;DR
The whole point is just that you can convert between float
and double
, but in general there is no conversion available between some_template<float>
and some_template<double>
. The type of a literal can be relevant, even though you don't mind conversions between float
and double
. Changing a 3.0f
to a 3.0
can make code break, that was fine otherwise