Date: Fri, 9 Jan 2026 18:53:03 +0000
On Fri, 9 Jan 2026 at 18:49, Jonathan Wakely <cxx_at_[hidden]> wrote:
>
>
> On Fri, 9 Jan 2026 at 17:19, David Brown via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>>
>>
>> On 09/01/2026 17:37, Steve Weinrich via Std-Proposals wrote:
>> > Consider two methods:
>> >
>> > void interest (double arg1, bool minor);
>> >
>> > void interest (double arg1, int factor);
>> >
>> > The call: interest(5.5, 5); is ambiguous because 5 will freely convert
>> > to a bool.
>>
>> No, it is not ambiguous - the best fit overload will be used. The rules
>> for overload resolution are a bit complicated, but in this case the
>> results are clear.
>>
>> <https://en.cppreference.com/w/cpp/language/overload_resolution.html>
>>
>> >
>> > I was wondering what y’all would think of narrowing this behavior by
>> > this addition:
>> >
>> > void interest (double arg1, *explicit* bool minor);
>> >
>> > Potentially, this could be applied to all arguments:
>> >
>> > void *explicit* interest (double arg1, bool minor);
>> >
>>
>> To me, a bigger issue is when you /don't/ have overloads :
>>
>>
>> void interest (double arg1, bool minor);
>>
>> In this situation, the implicit conversion of an int to a bool for
>> "interest(5.5, 5)" is likely to be an error on the programmer's side,
>> but it is accepted without question by C++ compilers. To avoid this,
>> some people use "strong bool" types that have much stricter implicit
>> conversions, such as :
>>
>> class Bool {
>> bool v;
>> public :
>> constexpr Bool(bool v) noexcept : v(v) {}
>> Bool(auto v) = delete;
>> explicit constexpr operator bool() const noexcept { return v; }
>> };
>>
>> Then you can declare the function :
>>
>> void interest (double arg1, Bool minor);
>>
>> and use it with a bool type as the second argument, but not with another
>> type that has an implicit conversion to bool.
>>
>
> A class seems overkill here, I would use a scoped enum type:
>
> enum class InterestType { Minor, Major };
>
> This won't allow any explicit conversions.
>
>
> But it seems like a badly-designed overload set anyway, since you have one
> function name "interest" but the functions do two different things. It
> would probably be better if you had names that make what they do clearer,
> calc_interest_factor and calc_interest_major_minor or something (I have no
> idea what the "minor" argument means, so better names are probably
> possible).
>
>
https://abseil.io/tips/148 is highly relevant here (and to the bugs
described by David Crocker elsewhere in the thread):
“Use overloaded functions (including constructors) only if a reader
looking at a call site can get a good idea of what is happening without
having to first figure out exactly which overload is being called.”
If you have overloads with the same number of parameters, they had damn
well better be (mostly) interchangeable, e.g. it doesn't really matter if
you call std::pow(5.0, 2L) instead of std::pow(5.0, 2.0) because they do
the same thing. If they perform different operations based on the type of
the second argument, that's a bad API.
>
>
> On Fri, 9 Jan 2026 at 17:19, David Brown via Std-Proposals <
> std-proposals_at_[hidden]> wrote:
>
>>
>>
>> On 09/01/2026 17:37, Steve Weinrich via Std-Proposals wrote:
>> > Consider two methods:
>> >
>> > void interest (double arg1, bool minor);
>> >
>> > void interest (double arg1, int factor);
>> >
>> > The call: interest(5.5, 5); is ambiguous because 5 will freely convert
>> > to a bool.
>>
>> No, it is not ambiguous - the best fit overload will be used. The rules
>> for overload resolution are a bit complicated, but in this case the
>> results are clear.
>>
>> <https://en.cppreference.com/w/cpp/language/overload_resolution.html>
>>
>> >
>> > I was wondering what y’all would think of narrowing this behavior by
>> > this addition:
>> >
>> > void interest (double arg1, *explicit* bool minor);
>> >
>> > Potentially, this could be applied to all arguments:
>> >
>> > void *explicit* interest (double arg1, bool minor);
>> >
>>
>> To me, a bigger issue is when you /don't/ have overloads :
>>
>>
>> void interest (double arg1, bool minor);
>>
>> In this situation, the implicit conversion of an int to a bool for
>> "interest(5.5, 5)" is likely to be an error on the programmer's side,
>> but it is accepted without question by C++ compilers. To avoid this,
>> some people use "strong bool" types that have much stricter implicit
>> conversions, such as :
>>
>> class Bool {
>> bool v;
>> public :
>> constexpr Bool(bool v) noexcept : v(v) {}
>> Bool(auto v) = delete;
>> explicit constexpr operator bool() const noexcept { return v; }
>> };
>>
>> Then you can declare the function :
>>
>> void interest (double arg1, Bool minor);
>>
>> and use it with a bool type as the second argument, but not with another
>> type that has an implicit conversion to bool.
>>
>
> A class seems overkill here, I would use a scoped enum type:
>
> enum class InterestType { Minor, Major };
>
> This won't allow any explicit conversions.
>
>
> But it seems like a badly-designed overload set anyway, since you have one
> function name "interest" but the functions do two different things. It
> would probably be better if you had names that make what they do clearer,
> calc_interest_factor and calc_interest_major_minor or something (I have no
> idea what the "minor" argument means, so better names are probably
> possible).
>
>
https://abseil.io/tips/148 is highly relevant here (and to the bugs
described by David Crocker elsewhere in the thread):
“Use overloaded functions (including constructors) only if a reader
looking at a call site can get a good idea of what is happening without
having to first figure out exactly which overload is being called.”
If you have overloads with the same number of parameters, they had damn
well better be (mostly) interchangeable, e.g. it doesn't really matter if
you call std::pow(5.0, 2L) instead of std::pow(5.0, 2.0) because they do
the same thing. If they perform different operations based on the type of
the second argument, that's a bad API.
Received on 2026-01-09 18:53:20
