Open h-2 opened 7 months ago
@llvm/issue-subscribers-clang-frontend
Author: Hannes Hauswedell (h-2)
I think clang is correct here. The cv-qualification in this case is part of the signature (if T is an array type, the two templates don't have the same parameter type after decay), so the templates don't have the same parameter types and so aren't ordered by their constraints.
if T is an array type, the two templates don't have the same parameter type after decay), so the templates don't have the same parameter types and so aren't ordered by their constraints.
Can you elaborate?
I am seeing this with Clang:
#include <concepts>
template <typename T>
void foobar(T) {}
template <std::regular T>
void foobar(T const) {}
int main()
{
using Arr = int[3];
foobar(Arr{1,2,3});
}
emits:
<source>:12:5: error: call to 'foobar' is ambiguous
12 | foobar(Arr{1,2,3});
| ^~~~~~
<source>:4:6: note: candidate function [with T = int *]
4 | void foobar(T) {}
| ^
<source>:7:6: note: candidate function [with T = int *]
7 | void foobar(T const) {}
| ^
1 error generated.
Compiler returned: 1
Which indicates that the template parameters are the same, or not?
template <typename T>
void foobar(T) { std::puts("non-const"); }
template <typename T> concept C = true;
template <C T>
void foobar(T const) { std::puts("const"); }
int main()
{
const int *p;
int *q;
foobar<int[1]>(p);
foobar<int[1]>(q);
}
[temp.func.order]/6.2.2 is the relevant rule:
Otherwise, if the corresponding template-parameters of the template-parameter-lists are not equivalent ([temp.over.link]) or if the function parameters that positionally correspond between the two templates are not of the same type, neither template is more specialized than the other.
Per [dcl.fct]/5, the type of the function parameter includes the top-level cv-qualifiers; those are removed when forming the function type, but retained in the parameter types. (CWG1001 is also relevant here, but in this case the wording is already clear.)
I've emailed CWG to check that this is the intended behavior.
Ah, this is interesting! I haven't passed around raw arrays in many years, so I didn't remember that rightmost const is pulled into the decayed pointer type. So you have made a case for when this distinction could be useful!
However, don't you agree that it is unexpected then that the concept is required at all? Since it isn't the thing that decides which template gets picked, correct? It's indeed the const
, so shouldn't this then work without the constraints? [spoiler: it does not]
Fun fact: GCC chokes on your example, because it creates the same mangled name for both templates.
I've emailed CWG to check that this is the intended behavior.
Cool, let's see what they say. I am not subscribed to the core reflector, so please let me know what comes up 🙂
#include <concepts> template <typename T> void foobar(T) {} template <std::integral T> void foobar(T const) {} int main() { foobar(3); }
The original case was fixed in #81449 (Godbolt link).
#include <concepts> template <typename T> void foobar(T) {} template <std::regular T> void foobar(T const) {} int main() { using Arr = int[3]; foobar(Arr{1,2,3}); }
This is also fixed (Godbolt link).
I think Clang is correct now - the top-level cv-qualifiers are dropped after determining the actual function type.
@frederick-vs-ja Per the discussion in this issue, I think clang was correct before. But let's wait and see what CWG says before reverting #81449.
Expected behaviour:
This code is well-formed; the second overload is chosen (because more constrained); the
const
is irrelevant.GCC and MSVC show this behaviour.
Actual behaviour:
Clang rejects this code: