I have this overloaded function in C :
template<std::size_t N>
void func(const int(&)[N]) {
std::cout << "1";
}
void func(const int*) {
std::cout << "2";
}
If I call func with a pointer, the second overload is called. This is expected.
const int* p{};
func(p);
// prints 2 - OK
If I call func with an int array, the first overload is called. This is also expected.
int ints[]{1, 2, 3, 4, 5};
func(ints);
// prints 1 - OK
However, if I have call func with an array of const ints, I expect the first overload to be called, but the second is called instead.
const int ints[]{1, 2, 3, 4, 5};
func(ints);
// prints 2 - NOT OK, 1 is expected
My question is why and how could I write the function func
to have the first overload called for const arrays as well? I need the array size in a template parameter, so anything like strlen is not a possible solution for me.
(It behaves the same way for other types too, like std::string.)
CodePudding user response:
Actually expectation should be that both functions are okey for the lines:
const int ints[]{1, 2, 3, 4, 5};
func(ints);
The only reason you do not get error it is because of it is a template. If you extract the first function from template you will get a compile error about ambiguity which is:
error: call of overloaded ‘func(const int [5])’ is ambiguous
If you edit your code accordingly:
void func(const int(&)[5]) {
std::cout << "1"<<std::endl;
}
void func(const int*) {
std::cout << "2"<<std::endl;
}
int main()
{
int ints[]{1, 2, 3, 4, 5};
func(ints);
const int ints2[]{1, 2, 3, 4, 5};
func(ints2);
return 0;
}
CodePudding user response:
In general you should not expect this overload resolution to work. An array-to-pointer conversion is equally good as an identity conversion.
What you need to do is forcibly remove the second overload from consideration when the first overload is usable. This can be done easily in C 20:
template <std::size_t N>
constexpr int func(const int(&)[N]) {
return 1;
}
namespace detail {
template <std::size_t N>
constexpr int dummy_func(const int(&)[N]);
}
template <class T>
constexpr int func(T&& r)
requires std::is_convertible_v<T, int const*> &&
(!requires { detail::dummy_func(std::forward<T>(r)); }) {
return 2;
}
Now you'll get the overload resolution behaviour that you want:
int main() {
constexpr int const* p = nullptr;
static_assert(func(p) == 2);
int const ca[5] = {1, 2, 3, 4, 5};
static_assert(func(ca) == 1);
int a[5] = {1, 2, 3, 4, 5};
static_assert(func(a) == 1);
}
Note that this solution is "imperfect" in some ways. The second overload will not be able to accept some arguments that a true const int*
parameter can accept, namely an empty braced-init-list or a braced-init-list with a single element that can be converted to const int*
.