Consider the following code:
#include <iostream>
float func(char const & val1, unsigned int const & val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
std::cout << func(test1, test2) << std::endl;
return 0;
}
This compiles and runs despite the fact that I am passing in a double
to a function that takes a const-reference to types that are smaller than a double
(on my system, sizeof(double) == 8
, while sizeof(unsigned int) == 4
, and sizeof(char) == 1
by definition). If the reference isn't const
, compilation fails (e.g., float func(char & val1, unsigned int & val2)
instead of the current definition) with an error:
cannot bind non-const lvalue reference of type 'char&' to an rvalue of type 'char'
I get the exact same behavior when testing this with GCC, Clang, ICC, and MSVC on Godbolt, so it appears standard. What is it about const-references that causes this to be accepted, whereas a reference isn't? Also, I used -Wall -pedantic
- why am I not getting a warning about a narrowing conversion? I do when the function passes by value instead of by reference...
It is indeed standard.
test1
and test2
are converted to anonymous temporary char
and unsigned
types for which the const
references in the function are appropriate bindings. If you set your compiler to warn you of narrowing conversions (e.g. -Wconversion), it would output a message.
These bindings are not possible if the function parameters are non-const
references, and your compiler is correctly issuing a diagnostic in that case.
One fix is to delete
a better overload match:
float func(double, double) = delete;
As a complement to the accepted answer, particularly the approach
One fix is to delete a better overload match:
float func(double, double) = delete;
one could also approach it from the other way around: namely deleting all overloads that are not exactly matching your intended types of parameters. If you want to avoid any implicit conversions (including promotions), you could define func
as a deleted non-overloaded function template, and define explicit specializations of func
only for the specific types of arguments you’d like to have overloads for. E.g.:
// Do not overload the primary function template 'func'.
// http://www.gotw.ca/publications/mill17.htm
template< typename T, typename U >
float func(const T& val1, const U& val2) = delete;
template<>
float func(char const& val1, unsigned int const& val2)
{
return val1 + val2;
}
int main() {
double test1 = 0.2;
double test2 = 0.3;
char test3 = 'a';
unsigned int test4 = 4U;
signed int test5 = 5;
//(void)func(test1, test2); // error: call to deleted function 'func' (... [with T = double, U = double])
//(void)func(test2, test3); // error: call to deleted function 'func' (... [with T = double, U = char])
(void)func(test3, test4); // OK
//(void)func(test3, test5); // error: call to deleted function 'func' (... [with T = char, U = int])
return 0;
}
Emphasizing again to take care if intending to overload the primary function template, as overload resolution for overloaded and explicitly specialized function templates can be somewhat confusing, as specializations do not participate in the first step of overload resolution.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With