3

As far as I know, the type of N in T[N] is std::size_t. I'm using C++17, in case it matters.

template<class T, T N>
void check(char const (&)[N]) 
{ std::cout << std::is_same_v<std::size_t, T> << '\n'; }

int main()
{
    char c[5];
    check(c);
    return 0;
}

This prints 1. However, this fails to compile:

template<std::size_t N>
void check(char const (&)[N]) { std::cout << "std::size_t"; }

template<int N>
void check(char const (&)[N]) { std::cout << "int"; }

int main()
{
    char c[5];
    check(c);
    return 0;
}
<source>: In function 'int main()':
<source>:17:12: error: call of overloaded 'check(char [5])' is ambiguous
     check(c);
            ^
<source>:9:6: note: candidate: 'void check(const char (&)[N]) [with long unsigned int N = 5]'
 void check(char const (&)[N]) { std::cout << "std::size_t"; }
      ^~~~~
<source>:12:6: note: candidate: 'void check(const char (&)[N]) [with int N = 5]'
 void check(char const (&)[N]) { std::cout << "int"; }
      ^~~~~

If N is std::size_t, isn't the first template overload the best match? I don't understand where the ambiguity is coming from.

2 Answers 2

4

Both overloads are viable.

Then we have to select the Best viable function

Your concern should be:

  1. there is at least one argument of F1 whose implicit conversion is better than the corresponding implicit conversion for that argument of F2, or, if not that,

Then in Ranking of implicit conversion sequences

  1. Exact match: no conversion required, lvalue-to-rvalue conversion, qualification conversion, function pointer conversion,(since C++17) user-defined conversion of class type to the same class
  2. Promotion: integral promotion, floating-point promotion
  3. Conversion: integral conversion, floating-point conversion, floating-integral conversion, pointer conversion, pointer-to-member conversion, boolean conversion, user-defined conversion of a derived class to its base

I don't see anything which won't qualify const char(&)[N] (with N either std::size_t nor int) as exact match.

And there are no other tie breaker.

So it is ambiguous.

As a note, they both have same signature, see:

template<std::size_t N> void check(char const (&)[N]) {}
template<int N> void check_int(char const (&)[N]) {}

static_assert(std::is_same_v<decltype(&check<5>), decltype(&check_int<5>)>); // Pass

Demo

You might also note that conversion ranking concerns function parameters, not conversion to template parameter.

template<std::size_t N> void ambiguous() {}
template<int N> void ambiguous() {}

int main()
{
    constexpr int n = 5;
    ambiguous<n>(); // Error
    constexpr int sz = 42;
    ambiguous<sz>(); // Error
}

Demo

Sign up to request clarification or add additional context in comments.

4 Comments

But N IS already a size_t by itself, regardless of which overload it's used right? I mean, if check as a function didn't exist at all, N is a size_t realdy. Isn't? Why isn't the first overload an exact match? An if it's not an "size_t" by itself, why in the first example it's resolved as size_t?
Both are exact match. char const (&)[5] -> char const (&)[N] with N=5.
The value of N is the same for both overloads but the type of N is different, and the type of N in the first overload matches exactly the type of N in char[N]. The first overload should win. I still don't understand.
template<int N> void check(char const (&)[N]) is template<int N> void check(char const (&)[static_cast<std::size_t>(N)]), so they have same signature.
3

the type of N in T[N] is std::size_t.

True, which means the two overloads have to be ambiguous.

Think about the implications of this requirement. Every time an array is declared, there is an implicit conversion of the size to std::size_t. It is as if each valid T[N] is replaced by T[static_cast<std::size_t>(N)], with emphasis on "valid". (If compilation fails without this replacement, the replacement is not done. For example, using a scoped enumeration (enum class) as the size will not compile even though adding the cast would make it compile.) For the question's code, this replacement preserves semantics.

Take another look at the declaration of the last template:

template<int N>
void check(char const (&)[N])

What is the type of the parameter? It is a reference to an array. Since it is an array, the size must be of type std::size_t, as you yourself pointed out. The compiler sees this declaration as

template<int N>
void check(char const (&)[static_cast<std::size_t>(N)])

This conversion is done before overload resolution sees the function signature. The type of this parameter is the same as the type of the parameter to the other template. Ambiguous.

Note that the same implicit conversion happens in the declaration char c[5], as your first example shows. The literal 5 has type int and gets converted to std::size_t in order to form the type more accurately written char [5uz] (typically char [5ull] pre-C++23). Whenever you see an array type, the size must be std::size_t regardless of the nominal type of the expression giving the size.

4 Comments

To match N = size_t(5) (the type and value of the array extension) with the first overload you don't need any implicit conversion of N at all, while you need a conversion from size_t to int for the second. The first overload should win.
No, you are thinking as if the conversion happens during overload resolution. The conversion happens earlier, when the declarations are initially processed. The conversion happens even if you never try to invoke check(). By the time overload resolution starts, the parameter types are already the same. If you want, you can think of the compiler changing each T[N] to T[static_cast<std::size_t>(N)] when encountered (but there might be some edge cases where the semantics are slightly different; I have not thought that through).
I've added more explanation to the answer. Does it make more sense now?
Yes it's. I was thinking on best match in terms of "template parameters", but actually, it's about the function signature, and the function signature is the same in both cases. Got it now.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.