4
template <class T>
void func(T*& a)
{

}

int main()
{
    int a[5];
    func(a); // <-- Error here: no matching function for call to ‘func(int [5])’

    return 0;
}

why int[] is not implicitly converted to int* so that it can match template functions?

Dai
  • 141,631
  • 28
  • 261
  • 374
N.Y.C
  • 43
  • 3
  • Why use `int*` in the first place? It's bad practice to use raw pointers in C++. – Dai Sep 02 '22 at 08:22
  • Post your code in a code block. Nobody wants to open an image of your code. While we're at it, provide a [Minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) – SimonC Sep 02 '22 at 08:22
  • @Dai Probably because a plethora of standard functions require pointers – SimonC Sep 02 '22 at 08:22
  • 2
    Because arrays are not pointers (and pointers are not arrays). What do you expect to happen if you did, for instance, `a = nullptr;` in that function? – molbdnilo Sep 02 '22 at 08:23
  • 1
    @molbdnilo [Arrays decay into pointers tho](https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-decay) – Dai Sep 02 '22 at 08:23
  • @Dai Yes. And `int` implicitly converts to `float`, but `int&` does not convert to `float&`. – molbdnilo Sep 02 '22 at 08:24
  • @molbdnilo Array covariance/contravariance is not the same thing as array pointer decay. – Dai Sep 02 '22 at 08:25
  • Are you asking why C++ designers made this choice and not another, or your question is about something else? – n. m. could be an AI Sep 02 '22 at 08:34
  • @n.1.8e9-where's-my-sharem. I think the OP is just surprised at C++'s inconsistent rules for array decay and implicit type conversions - so their question still is asking **why** an `int[5]` argument is not implicitly convertible to parameter type `T*&`. – Dai Sep 02 '22 at 08:36
  • Can I understand that this is because the array name doesn't decay to a pointer when it is an argument to the reference operator(&) ? – N.Y.C Sep 02 '22 at 08:38
  • @Dai I don't see how co-/contravariance is relevant (there is no inheritance relationship involved). The fact that `A` converts to `B` does not imply that `A&` converts to `B&`. – molbdnilo Sep 02 '22 at 08:38
  • @Dai "Why" is ambiguous. It could mean "which exact rule is responsible for this" or "why do we have this rule". – n. m. could be an AI Sep 02 '22 at 08:46
  • @molbdnilo Sorry, I misread your comment. – Dai Sep 02 '22 at 08:51
  • @N.Y.C BTW, if you change your code to `void func(T* a)` (i.e. remove the `&`) then it compiles fine (just don't forget to always pass another parameter: a `size_t` value carrying the `sizeof a`). However, I'm wondering if you were thinking "reference to an array element" or "reference to a pointer" or something in-between? If so, then alternative solutions do exist, however you really should be using `std::array` to represent compile-time fixed-size arrays (e.g. stack-allocated arrays like `int a[5]`). – Dai Sep 02 '22 at 09:00

3 Answers3

6

There is an implicit conversion from int[5] to int*, but an int[5] is not an int*. Thus there is no int* for the int*& parameter of func<int> to reference.

This situation is no different from trying to pass an int to a function expecting a float&. Just because an int can be implicitly converted to a float doesn't mean an int is a float. The exact same thing applies to int[5] and int*. Just because one can be implicitly converted to the other doesn't mean that they are the same thing.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • But you can get an `int*` to the first element in an `int[5]`, and then pass that `int*` around - like we do in C. (Note that I wouldn't personally ever do any of this: I'd use `std::array` and avoid this whole problem) – Dai Sep 02 '22 at 08:41
  • 1
    @Dai Sure, if you create an `int*` variable for the `int*&` to reference then it can reference it. No such object exists in this case (and non-const references can't reference temporaries, so even if template argument deduction considered it a temporary created from converting an `int[5]` to `int*` couldn't bind to the function parameter). – Miles Budnek Sep 02 '22 at 08:43
  • I think your comment (immediately above) is a better explanation than your current answer as-is, imo, as I think the OP (and myself, when I was cutting my teeth on C++) seem to believe that `T[]` is always reducible to `T*` and that `T*&` is (somehow...) always substitutable for `T*` - at least, that's the vibe I'm getting. – Dai Sep 02 '22 at 08:44
2

There are actually two issues. One is about how references work, and the other is about how templates work.

Let's examine them separately.

Consider these non-template functions:

void foo(int*&) {}
void bar(int* const &) {}
void baz(int*) {}

The second and third functions accept array arguments, but the first one does not. That's because arrays decay to temporary pointers. Non-const references don't bind to temporaries, but const references work just fine. The third function works just fine too because a temporary can be passed by value.

Now consider templates. If we turn the above functions into templates, only the third function works, the first two do not. Why is that?

That's because T* does not match int[5], so deduction fails in the first two cases. It does not fail in the third case because there is a special provision for it in the standard.

If P is not a reference type:

  • If A is an array type, the pointer type produced by the array-to-pointer standard conversion is used in place of A for type deduction; ...

So when the parameter type is T* (not a reference), int[5] is replaced with int*, and lo and behold,T* matches int*, so T can be deduced. But when the parameter is a reference, no such replacement is made.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • Thank you very much for solving my problems and letting me learn a lot of new things at the same time. – N.Y.C Sep 02 '22 at 09:25
1

There are 2 things that are important here:

  1. Since the parameter a is a reference type, the passed argument is not passed by value. In other words, conversion like array to pointer decay will not happen for the passed argument.

  2. Since the parameter is a reference to a pointer to a T, the passed argument must be of a pointer type.

Now, lets apply this to your example:

By writing func(a), you're passing the argument a which is of array type int [5] by reference so that it will not decay to a pointer to int(int*). In other words, the parameter expects that you pass an argument of pointer type by reference but you're passing an array type by reference(so that there is no decay to pointer type) and hence the error.

Jason
  • 36,170
  • 5
  • 26
  • 60