35

Can any one help me understand the following code

#include <iostream>

void foo(const char * c)
{
   std::cout << "const char *" << std::endl;
}

template <size_t N>
void foo(const char (&t) [N])
{
   std::cout << "array ref" << std::endl;
   std::cout << sizeof(t) << std::endl;
}

int main()
{
    const char t[34] = {'1'};
    foo(t);

    char d[34] = {'1'};
    foo(d);
}

The output is

const char *
array ref
34

Why does the first foo calls the const char * version ? How can I make it call the reference version ?

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
user511274
  • 503
  • 4
  • 8

3 Answers3

15

Conversion of const char[N] to const char* is considered an "exact match" (to make literals easier, mainly), and between two exact matches a non-template function takes precedence.

You can use enable_if and is_array to force it to do what you want.


A messy way to force it might be:

#include <iostream>

template <typename T>
void foo(const T* c)
{
   std::cout << "const T*" << std::endl;
}

template <typename T, size_t N>
void foo(const T (&t) [N])
{
   std::cout << "array ref" << std::endl;
}

int main()
{
    const char t[34] = {'1'};
    foo(t);

    char d[34] = {'1'};
    foo(d);
}

/*
array ref
array ref
*/

I realise that the OP had char not some generic T, but nonetheless this demonstrates that the problem lay in one overload being a template and not the other.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • 1
    How do you explain that the second version of `foo()` is called the second time ? – fouronnes Mar 02 '11 at 21:27
  • 1
    @otibom: Conversion of `char[N]` doesn't have the same rule. `char[N]` to `const char*` is not an exact match: it's convertible, but the template function matches more easily. – Lightness Races in Orbit Mar 02 '11 at 21:33
  • @otibom The first version ain't const, but the template version takes a const array pointer. – Lundin Mar 02 '11 at 21:36
  • 1
    @otibom: Conversion from `char[34]` to `char*` or to `const char (&) [34]` is "exact match", but conversion from `char[34]` to `const char*` is a pointer conversion, which does make a difference in overloading. – aschepler Mar 02 '11 at 21:39
  • @aschepler Why is conversion from `char[34]` to `const char (&)[34]` considered an "exact match"? Can you explain in more detail? – NPS Aug 10 '13 at 17:24
  • @NPS: Because table 12 in the standard says so ("qualification conversions") — find it within 13.3.3.1.4. On the other hand, in `char[34]`->`const char*`, the array-to-pointer conversion is actually "exact match" as well, but you then need a second conversion to get the `const`. – Lightness Races in Orbit Aug 11 '13 at 12:05
  • @LightnessRacesinOrbit No, I meant why there's no `const` conversion required in `const char (&)[34]` case? – NPS Aug 11 '13 at 21:20
  • @NPS: There is, and that's the _only_ conversion that need be performed. – Lightness Races in Orbit Aug 11 '13 at 21:24
  • @LightnessRacesinOrbit "the array-to-pointer conversion is actually "exact match" as well" - Shouldn't an "exact match" need **no** conversions? Maybe I don't understand what an "exact match" is. – NPS Aug 12 '13 at 13:49
  • GCC gives an ambiguous overload error for the code in this answer: http://ideone.com/G4spM7 – interjay Mar 23 '14 at 19:09
  • @interjay: Will investigate tomorrow – Lightness Races in Orbit Mar 24 '14 at 01:11
5

Let's look at this modified example with no template.

void foo(const char * c)
{
    std::cout << "const char *" << std::endl;
}

void foo(const char (&t) [34])
{
    std::cout << "const char (&) [34]" << std::endl;
}

int main()
{
    const char t[34] = {'1'};
    foo(t);
}

My compiler says call of overloaded foo is ambiguous. This is because conversions from array to pointer are considered an "Exact" conversion sequence and are not better than the null conversion sequence for overload resolution (Standard section 13.3.3.1.1.)

In the original code, the template parameter N can be deduced as 34, but then both non-template foo(const char*) and foo<34>(const char (&)[34]) are considered in overload resolution. Since neither is better than the other by conversion rules, the non-template function beats the template function.

Fixing things seems tricky. It seems like the is_array template from header <type_traits> (from C++0x if possible or Boost if not) might help.

aschepler
  • 70,891
  • 9
  • 107
  • 161
1

This appears to be different for various compilers.

Mircosoft and Borland both use the const char* version, while GNU is giving the output you described.

Here is a snippet from the C++ standard:

14.8.2.1 Deducing template arguments from a function call [temp.deduct.call]

Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.

If P is not a reference type:

-- If A is an array type, the pointer type produced by the array-to-pointer standard conversion (4.2) is used in place of A for type deduction; otherwise,

-- If A is a function type, the pointer type produced by the function-to-pointer standard conversion (4.3) is used in place of A for type deduction; otherwise,

-- If A is a cv-qualified type, the top level cv-qualifiers of A's type are ignored for type deduction.

If P is a cv-qualified type, the top level cv-qualifiers of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction

The compiler will build an A list as follows:

Argument:        t                 d
A:          char const[34]      char[34]

And parameter list P:

Parameter:       c                 t
P:            char const*       char const& t[N]

By default the compiler should choose non-referenced parameters. GNU is dong it wrong the second time for some reason.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
Millianz
  • 161
  • 8