10

I want to be able to differentiate array from pointers in overload resolution :

class string {
public:
        string(const char* c_str);

        template<int N>
        string(const char (&str) [N]);
};


int main() {
        const char* c_str = "foo";
        string foo(c_str);      // ok will call string(const char*)

        string bar("bar");      // call string(const char*) instead of the array version
}

The best I have found so far is to use a reference to the pointer instead of a pointer :

class string {
public:
        string(const char*& c_str);

        template<int N>
        string(const char (&str) [N]);
};


int main() {
        const char* c_str = "foo";
        string foo(c_str);      // ok will call string(const char*)
        string bar("bar");      // ok, will call the array version
}

it's not exactly the same thing and I want to know if a better way exist

pan-
  • 143
  • 9
  • Why do you expect a difference between the calls, and what's your intend to use such a (non-existent) side effect? BTW naming a class `string` outside of dedicate namespace is calling for trouble. – πάντα ῥεῖ Feb 23 '14 at 18:41
  • I expect differences because, when i call `string bar("bar")`, I don't pass a pointer but an array, that's were the array to pointer decay kicks in and I want to get the size of the array at compile time if it's available, `memcpy` is far better than `strcpy`. Otherwise, this class has it's proper namespace ofc, I drop it for clarity. – pan- Feb 23 '14 at 18:44
  • If the array is static I can, try the second snippet of code, you will see, the constructor with templated array parameter is called on the second call (`string bar("bar")`) . what I want is a better way to discriminate pointer from arrays. – pan- Feb 23 '14 at 18:57
  • Is an `init` function ok? I'm not sure how to do it with constructors. – chris Feb 23 '14 at 19:05
  • It will be better with constructor but it's always good to know how to do that :) – pan- Feb 23 '14 at 19:08
  • @pan-, How about a `make_string` function? You could have that on top of your existing constructor that only works with pointers. – chris Feb 23 '14 at 19:12
  • why not, my question is more general anyway, how to discriminate array from pointers in overload. I don't know if you can use SFINAE in these case – pan- Feb 23 '14 at 19:14
  • @pan-, Ok yeah, I failed at SFINAE the first time and then realized it could be used the exact same way as the other thing I was doing. – chris Feb 23 '14 at 19:31

3 Answers3

13

You need to make the first overload a poorer choice when both are viable. Currently they are a tie on conversion ranking (both are "Exact Match"), and then the tie is broken because non-templates are preferred.

This ought to make the conversion ranking worse:

struct stg
{
    struct cvt { const char* p; cvt(const char* p_p) : p(p_p) {} };

    // matches const char*, but disfavored in overload ranking
    stg(cvt c_str); // use c_str.p inside :(  Or add an implicit conversion

    template<int N>
    stg(const char (&str) [N]);
};
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
6

You can use SFINAE. This might not be the best way, but it should work ok:

//thanks to dyp for further reduction
template<typename T, typename = typename std::enable_if<std::is_same<T, char>::value>::type>
string(const T * const &) {std::cout << "const char *\n";}

template<std::size_t N> //credit to jrok for noticing the unnecessary SFINAE
string(const char(&)[N]) {std::cout << "const char(&)[" << N << "]\n";}

Here's a live example.

chris
  • 60,560
  • 13
  • 143
  • 205
  • Indeed it will do exactly the same thing as passing a reference to the pointer because the signature of the friend function is `string make_string(const T &)`. Maybe that's the only way, I'm trying with SFINAE but I have no result so far. Thank you for your answer – pan- Feb 23 '14 at 19:34
  • @pan-, I've since updated my answer to use SFINAE, since it's doing the same thing and I just didn't realize it before. There's another update now to allow for `char *`. – chris Feb 23 '14 at 19:37
  • I've seen the update after I reply, it's far better, and it can work on non reference to pointer : http://coliru.stacked-crooked.com/a/1c8faf4816d1b84c thank you very much – pan- Feb 23 '14 at 19:41
  • @pan-, Ah, good point. Make sure you get the updated code if you want that to do `char *` as well. I'm not aware of any better way to add it in than a separate check altogether since the `const` is hidden behind a pointer. Maybe `std::remove_pointer`, but then you have to check that it's a pointer to begin with for it to work. – chris Feb 23 '14 at 19:43
  • [You don't need SFINAE on the second overload](http://coliru.stacked-crooked.com/a/2b226ec6ee073cca), even. – jrok Feb 23 '14 at 20:47
  • @jrok, Good point. I'm pretty sure I was planning to do that when I formed the first one and then ended up copy-pasting it and going from there. Mind if I add that into the answer? As for the `remove_p_cv`, it unfortunately then accepts `std::string s('a');`. – chris Feb 23 '14 at 20:54
  • Good catch on remove_p_cv! :o – jrok Feb 23 '14 at 21:07
  • There's another fancy method: `template::value >::type > string(const T* const& c_str)` (works with both clang++3.5 and g++4.8.1, and arrays, `char*`, `char const*`) – dyp Feb 23 '14 at 21:14
  • @dyp, Thank you, I can't believe I didn't realize that! I totally forgot the real reason `char *` wasn't working before. – chris Feb 23 '14 at 21:19
  • @chris I'm still wondering *why* my suggestion works. The first template won't bind directly to a string literal, but only requires an Lvalue Transformation (plus a Qualification Conversion?). – dyp Feb 23 '14 at 21:22
  • @dyp, Ah, I see your point. I'm pretty sure it's the reference that's bugging the string literal. Not completely sure why, though. – chris Feb 23 '14 at 21:27
  • @chris .. it turns out to be a *deduction* issue, not an overload issue. [temp.deduct.call]/2 says the array-to-pointer conversion is only performed if the template parameter is not a reference. – dyp Feb 23 '14 at 22:10
  • @dyp, That's interesting. – chris Feb 23 '14 at 22:16
0

A more generic version of this problem could be detected as follows.

template <class T>
void func(T, 
          typename std::enable_if<std::is_pointer<T>::value, void>::type * = 0)
{
  // catch ptr
}

template <class T, int N>
void func(T (&)[N])
{
  //catch array
}

int main(void)
{
  int arr[5];
  char *b = 0;
  func(arr); // catch array
  func(b);   // catch ptr
}
Sumant
  • 4,286
  • 1
  • 23
  • 31