nothing interesting to say before the cut, turbo nerd stuff to follow
so, C++ has two very closely related datatypes: arrays and pointers. in principle there's no reason for them to be so closely related, it's just a holdover from C. it's occasionally a useful concept, but more often than not it's an excuse to use unsafe, dynamic constructs to manipulate data that was originally specified in a safe, statically checkable way.
it's important to note here that arrays in C++ do have a statically known, easily queryable size. it's just that the way you query it looks very different from languages like C# and Java, requiring you to disassemble the array's type (typically via a std::size_t template parameter). you can see this in the utility function template<typename T, std::size_t N> std::size(T const (&array)[N]) from the STL, which simply returns N.
arrays have this particularly odd property where they can "decay" into a pointer: if you pass an array into basically any function expecting a pointer, it will Just Work™️. this is a very dangerous process, because it discards the size information that is included in an array's type. even worse, this conversion is implicit: even if f(my_array) compiles, you cannot assume f is written in a way to take advantage of the size information included in the type of my_array.
now, on to the thing i actually want to talk about. whenever you call a function foo(thing) in C++, the first thing that happens is the compiler builds an "overload set", which includes all possible versions of foo that could reasonably apply to the type of thing. then, the compiler chooses the "closest match" from that overload set using a bunch of really convoluted and tedious rules, and after a little bit more checking (stuff like making sure the function isn't = delete or constexpr and not implemented or whatever), a call to the selected function is generated.
now. you would think. you would think that the best match for a call to foo(some_array) would be one matching an array type. YOU WOULD THINK. but C++ disagrees with you:
#include <iostream>
template<std::size_t SIZE>
void foo(char const (&data)[SIZE])
{
(void) data;
std::cout << "array version\n";
}
void foo(char const *data)
{
(void) data;
std::cout << "pointer version\n";
}
int main()
{
char const abc[] = "abc";
foo("abc");
foo(abc);
}
both calls print pointer version. it always decays the argument before building the overload set, unconditionally throwing away the very useful, zero-cost, statically-known size information. i have no idea why. edit: it's because between a template and a non-template, the non-template always wins. even if the template doesn't require any conversions or decays and the non-template does!
the only way to fix this that i'm aware of is to make sure that any function that has both an array and a pointer overload is to always pass a size along with the pointer version:
#include <iostream>
template<std::size_t SIZE>
void foo(char const (&data)[SIZE])
{
(void) data;
std::cout << "array version\n";
}
void foo(char const *data, std::size_t size)
{
(void) data;
(void) size;
std::cout << "pointer version\n";
}
int main()
{
char const abc[] = "abc";
foo("abc");
foo(abc);
foo("abc", 4);
foo(abc, 4);
}
now the first two calls print array version, and the second two print pointer version.
i_guess.jpg

eggbug enjoyer