mpeschke
mpeschke

Reputation: 448

Inconsistent implicit conversion behavior

I encountered a case where I cannot understand the implicit conversion behavior in c++. The code is the following:

template <bool b, int i, unsigned... us>
void foo() {}

template <int i, unsigned... us>
void foo() {return foo<false, i, us...>();}

int main()
{
    foo<true, -1, 0ul, 1ul, 2ul>(); // compiles with clang and gcc > 8 (gcc<=8 gives ambiguous function call)
    foo<true, +1, 0ul, 1ul, 2ul>(); // ambiguous function call error
    foo<-1, 0ul, 1ul, 3ul>(); // compiles with clang and gcc > 8 (gcc<=8 gives ambiguous function call)
    foo<+1, 0ul, 1ul, 3ul>(); // ambiguous function call error
}

For line 1 and 2 in main(), the compiler seems to implicitly convert int to unsigned only for positive integers (this is understandable but is there a rule for this?)

For line 3 and 4 in main(), the compiler seems to implicitly convert int to bool only for positive integers. I cannot understand why.

Background: Ideally, I want to get a similar construct to work which effectly leads to a default value for the bool template parameter. But because of the parameter pack and c++ implicit builtin conversions, this seems to be difficult.

Upvotes: 2

Views: 120

Answers (2)

mpeschke
mpeschke

Reputation: 448

The answer of @user17732522 completely explains the conversion behavior.

Here is a hack to achieve a default template parameter for the bool template parameter of the function:

template<bool b>
struct Dummy {};

template <int i, unsigned... us, bool b>
void foo(Dummy<b>) {}

template <int i, unsigned... us>
void foo() {return foo<i, us...>(Dummy<false>{});}

int main()
{
    foo<-1, 0ul, 1ul, 2ul>(Dummy<true>{});
    foo<+1, 0ul, 1ul, 2ul>(Dummy<true>{});
    foo<-1, 0ul, 1ul, 3ul>();
    foo<+1, 0ul, 1ul, 3ul>();
}

Upvotes: 0

user17732522
user17732522

Reputation: 76658

A non-type template argument is required to be a converted constant expression of the template parameter's type.

A converted constant expression specifically does not allow for narrowing conversions, which in the case of constant expression evaluation between integral types means that the conversion is not allowed if it would change the numeric value.

This is specific to the converted constant expression requirement. If these were function parameter and argument, then implicit conversion of -1 to unsigned would be allowed and it also has a well-defined result, although obviously not with the same numeric value of -1.

Therefore in foo<true, -1, 0ul, 1ul, 2ul>(); the template which requires unsigned in that position for -1 is indeed not viable as an overload candidate.

Boolean conversions, meaning conversions from other scalar types to bool are not listed in the list of conversions allowed for a converted constant expression at all (see [expr.const]/10), which as far as I can tell should make the overload with bool in the first position non-viable for both foo<-1, 0ul, 1ul, 3ul>(); and foo<+1, 0ul, 1ul, 3ul>();. I am not sure why the compilers consider the latter ambiguous anyway. According to the resolution of CWG issue 1407 as not-a-defect, the conversion from int template argument to bool template parameter is indeed not allowed, but compilers are behaving non-conforming and seem to treat it like an integral type with range 0/1.

I don't know what problem GCC <8 has. I guess it was just a bug that it considered the overload ambiguous.

Upvotes: 3

Related Questions