0

while I came across this snippet in <<C++ Primer>>

template< unsigned N, unsigned M>
int compare(const char(&p1)[N], const char (&p2)[M]
{
    return strcmp(p1, p2);
}
compare("hi", "mom")

sure it worked very well then i thought what if if remove const and & ? so i wrote this

template< unsigned N, unsigned M>
int compare(char p1 [N], char p2 [M])
{
    return strcmp(p1, p2);
}
compare("hi", "mom")

but i got this error

prog.cc: In function 'int main()':
prog.cc:34:12: error: no matching function for call to 'compare(const char [3], const char [4])'
   34 |     compare("hi", "mom");
      |     ~~~~~~~^~~~~~~~~~~~~
prog.cc:27:5: note: candidate: 'template<unsigned int N, unsigned int M> int compare(char*, char*)'
   27 | int compare(char p1 [N], char p2 [M])
      |     ^~~~~~~
prog.cc:27:5: note:   template argument deduction/substitution failed:
prog.cc:34:12: note:   couldn't deduce template parameter 'N'
   34 |     compare("hi", "mom");
      |     ~~~~~~~^~~~~~~~~~~~~

Why doesn't this work?

charlie
  • 267
  • 1
  • 3
  • 15
  • 2
    You might want to focus on one change at a time. That is, bring back the `const` and see what happens if you remove just the `&`. – JaMiT May 30 '21 at 02:34
  • I thied, it did't work either. I Got error message like this```no matching function for call to 'compare(const char [3], const char [4]); candidate: 'template int compare(const char*, const char*)'``` – charlie May 30 '21 at 02:51
  • 1
    You can't pass a C-style array by value, only by reference. And string literals specifically must always be passed by const reference. – Patrick Roberts May 30 '21 at 02:59
  • 2
    @cirfe So you see my point? Bringing back the `const` gives you almost the same error message. The only difference is that `const` was brought into the signature of the candidate function, which is to be expected. **You have the same error if you keep `const`.** So why not strip away inconsequential details? Instead of *"what if [I] remove `const` and `&` ?"*, ask *"what if [I] remove `&` ?"*. The fewer differences between your working and not-working code, the fewer red herrings you'll have to sniff. – JaMiT May 30 '21 at 05:44
  • I found curious that the template `compare` function does not actually use `N` nor `M` template parameters. It should use them to optimize and secure against non-null terminated strings. Simply with `return memcmp(p1, p2, std::min(N, M));` – prapin May 30 '21 at 07:16

1 Answers1

1

Neither in C nor in C++ there exists a straight-forward manner to pass a C-style array by value (see e.g. here). A C-style array - if not wrapped by a class or struct - is actually only passed as a pointer. This also leads to problems with template deduction as a pass by value decays to a simple pointer and therefore the compiler can't deduce the template argument. Therefore the common approach is to either pass it by const or non-const reference or to switch to an std::array if a copy is needed. I don't think the second version of your template actually does what you want it to do.


Your initial code

template <unsigned N, unsigned M>
int compare(char const (&p1)[N], char const (&p2)[M]) {
  return std::strcmp(p1, p2);
}

is working fine. A reference to an array does not decay to a pointer: Therefore when calling the template with compare("hi", "mom") the compiler is able to deduce N to 3 and Mto 4 (+1 because it is null-terminated).


There area a couple of options for avoiding this mess altogether:

  • You could pass the array by reference and then create your local copy manually.

  • You could wrap it with a struct. This will automatically create a copy of the wrapped C-style array.

  • You could use an std::array<char,N> instead which can be passed by value like a regular variable. When using it with std::strcmp - like in your case - you would though actually have to copy the data into another C-style array though.

  • You could pass an std::string as input argument and only then convert it to a char array for std::strcmp

    int compare(std::string const& p1, std::string const& p2) {
      return std::strcmp(p1.c_str(), p2.c_str());
    }
    

    or use the std::string comparison operator instead as well

    int compare(std::string const& p1, std::string const& p2) {
      return (p1 == p2);
    }
    

    This allows you to get rid of the template completely.

2b-t
  • 2,414
  • 1
  • 10
  • 18
  • String literals are lvalues, aka not temporaries. They can't be passed to non-const pointers/references simply because they're const. – HolyBlackCat May 30 '21 at 07:34
  • @2b-t You're welcome. I suggest fixing the answer to not confuse future readers. – HolyBlackCat May 30 '21 at 15:25
  • @HolyBlackCat I am already working on it, thanks! – 2b-t May 30 '21 at 15:28
  • @JaMiT As soon as you add the template arguments it then it will [issue a warning `warning: ISO C++ forbids converting a string constant to 'char*`](https://wandbox.org/permlink/scQUcek3vEZJLATD) and with [`-pedantic-erros` this will be an error](https://wandbox.org/permlink/PJbIzgBVwlmZDwD9). – 2b-t May 31 '21 at 07:03
  • @JaMiT I am not sure if I understand what you are criticising. The OP asked for why this is not working and I gradually changed the code from his/her first version to his/her second version pointing out what the problems are and why you should actually pass it by reference. I added now a brief remark on C-style arrays and that it is in fact only a pointer as well as decay to pointers for arrays if passed by value which is the reason why the compiler can't deduct it. Let me know if that still is not a sufficient answer to this question in your opinion. – 2b-t Jun 01 '21 at 00:50
  • 1
    `std::array` has a `data()` function so there is no need to copy the data to compare it (unless it is not '\0' terminated). – Phil1970 Jun 01 '21 at 01:35