5

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...

curiousguy
  • 8,038
  • 2
  • 40
  • 58
R_Kapp
  • 2,818
  • 1
  • 18
  • 32
  • Narrowing conversions are only ever mentioned in the standard in relation to [list initialization](http://eel.is/c++draft/dcl.init.list#7). You aren't doing those so you won't get a narrowing conversion warning. See also https://stackoverflow.com/questions/52443036/why-doesnt-c-show-a-narrowing-conversion-error-when-casting-a-float-to-a-char/52443287 – Max Langhof Jun 12 '19 at 16:01

2 Answers2

9

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;
Bathsheba
  • 231,907
  • 34
  • 361
  • 483
  • Is `-Wall -pedantic` not sufficient? I have that set (as mentioned in the question) – R_Kapp Jun 12 '19 at 16:01
  • Nope. There's a separate one for narrowing conversions. – Bathsheba Jun 12 '19 at 16:02
  • @R_Kapp [Use `-Wconversion`](https://stackoverflow.com/questions/52443036/why-doesnt-c-show-a-narrowing-conversion-error-when-casting-a-float-to-a-char/52443287) – Max Langhof Jun 12 '19 at 16:02
  • 6
    I really wish gcc had a `-Wabsolutely-everything-and-if-i-complain-im-an-idiot` option. Does `-Wextra` give you `-Wconversion`? – Omnifarious Jun 12 '19 at 16:03
  • @Omnifarious: `warning: you are still using c++ [-Wfind-a-pub-instead]` – Lightness Races in Orbit Jun 12 '19 at 16:04
  • @Omnifarious Well, good luck: https://stackoverflow.com/questions/11714827/how-to-turn-on-literally-all-of-gccs-warnings – Max Langhof Jun 12 '19 at 16:04
  • 2
    @Omnifarious: If I ever managed to get onto the gcc development team (Heaven forbid!) that would be my first project. – Bathsheba Jun 12 '19 at 16:04
  • `-Wall -Wextra -pedantic` usually suffices for me. I don't think that combination has ever _seriously_ let me down. – Lightness Races in Orbit Jun 12 '19 at 16:06
  • 1
    @MaxLanghof - The reasoning given misses one important case: I want to know what warnings there are so I can turn off the not-useful ones myself. The manual is not sufficient for a wide variety of reasons. Even if it were complete (which I suspect it frequently isn't) the explanations in the manual only go so far. Really, I want to know what kind of code triggers the warning, then go investigate and figure out if I actually care or not. – Omnifarious Jun 12 '19 at 16:09
  • @Omnifarious [you don't want that](https://quuxplusone.github.io/blog/2018/12/06/dont-use-weverything/). – n. m. could be an AI Jun 12 '19 at 16:13
  • `-Wextra` does not add this either - we need to add `-Wconversion` separately, it seems. – R_Kapp Jun 12 '19 at 16:13
  • @Omnifarious • For clang, I use `-Weverything` all the time. It's great; you want it. GCC should add that as a switch option. – Eljay Jun 12 '19 at 16:31
  • @Omnifarious Such a proposition has already been declined for gcc. [link](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=31573) – François Andrieux Jun 12 '19 at 17:09
  • @n.m. - I do, in fact, want that, for just the purpose mentioned in the post you link to. In order to discover interesting warning flags that I would like to enable everywhere. – Omnifarious Jun 12 '19 at 18:42
  • @Bathsheba anyone can submit a gcc patch. BTW what other warnings for legal, correct code did you have in mind? – M.M Jun 14 '19 at 07:03
4

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.

dfrib
  • 70,367
  • 12
  • 127
  • 192