10

Assume following code:

#include <iostream>

template<typename... T>
void foo(const T &...);

template<unsigned N>
void foo(const char (&)[N])
{
   std::cout << "char(&)[N]" << std::endl;
}

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

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

template<typename First, typename... T>
void foo(const First & first, const T &... rest)
{
   std::cout << "Generic + " << sizeof...(T) << std::endl;
   foo(first);
   foo(rest...);
}

int main()
{
    const char * c = "asdf";
    char a[] = {'a', 'b', 'c', 'd'};
    foo('f', c, a, 1);
    foo(a);
}

The output is:

Generic + 3
Single              // fine; 'f' is `char` -> generic
Generic + 2
const char *        // fine; c is `const char *`
Generic + 1
const char *        // (!) not fine
Single
char(&)[N]          // fine; a is char[4]

The last call - foo(a), where a is char[4] - calls the version I'm expecting - template<unsigned N> void foo(const char (&)[N]). But why doesn't instantiation of variadic template of foo call foo(const char (&)[N], but calls foo(const char *) instead? If there wasn't a char array overload, that should be expected - but why is it happening here? Shouldn't const First & capture array type properly?

Also, what would be the easiest way to make the generic variadic version work properly with arrays passed to it?


As Matthieu M. noticed in comments, the problem probably isn't caused by variadic templates, but by indirection:

#include <iostream>

template <unsigned N>
void foo(const char (&)[N])
{
   std::cout << "char(&)[N]" << std::endl;
}

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

template <typename T>
void goo(T const& t) {
    foo(t);
}

int main()
{
    char a[] = {'a', 'b', 'c', 'd'};
    foo(a);
    goo(a);
}
    char(&)[N]
    const char *

He also said that it might be compiler bug - although the code yields exactly same results in both Clang 3.2 dev, G++ 4.6 and 4.7.

R. Martinho Fernandes noted that changing a's type in the last snippet to const char a[] makes code yield const char * twice.

Griwes
  • 8,805
  • 2
  • 43
  • 70
  • 1
    Why `// fine; "afas" is const char *`? It is not! http://ideone.com/4KewDe – R. Martinho Fernandes Oct 28 '12 at 13:32
  • 2
    I managed to reduce the problem further [here](http://liveworkspace.org/code/72b3963b4f0c2d00592b4ce00f08fc82). Apparently the indirection is causing the issue and variadic have nothing to do with this. Still have not found a likely explanation though... It certainly looks like a compiler bug to me. – Matthieu M. Oct 28 '12 at 13:46
  • @MatthieuM. [Mind the const](http://liveworkspace.org/code/f1eb7dd2a208af9c4c784c553f7cbf1d). – R. Martinho Fernandes Oct 28 '12 at 13:52
  • @R.MartinhoFernandes: Indeed, removing it in `goo` is quite... [interesting](http://liveworkspace.org/code/7adb0a6d2606c17d2888bb31a308a136). Reminds me of a citation that all sufficiently advanced technology [looks like magic](http://liveworkspace.org/code/3a392d1cdd046140b9fe25d7e14fa13c)... I truly am at a loss, I see neither rhyme nor reason... – Matthieu M. Oct 28 '12 at 13:54
  • 3
    see here: http://stackoverflow.com/questions/5173494/pass-reference-to-array-in-c It is good explained that this is because of exact match of non template function.... – PiotrNycz Oct 28 '12 at 14:03
  • @PiotrNycz has it. I remember that previous question now, but this one still shocked me today. – aschepler Oct 28 '12 at 14:13
  • 1
    @PiotrNycz: Damned! Black magic it was then (how I wish the "array" type was stronger...) – Matthieu M. Oct 28 '12 at 14:16

1 Answers1

5

I think I can answer the second section: How to fix it? I'm not sure if I got the right answer because the case of passing a string literal produces, in my opinion, still the wrong result. The basic idea to the fix is to use perfect forwarding rather than hoping that the correct type is deduced for T const&. Why using T const& with an array causes the array to decay, I haven't quite figured, though.

Using perfect forward, of course, means that the function doing it is a perfect match and some of the specialization actually do some conversions, at least, adding const. Thus, it is necessary to use a different name. In total, this looks like this:

#include <iostream>
#include <utility>

void foo()
{
}

template<unsigned N>
void special(const char (&)[N])
{
    std::cout << "char(&)[" << N << "]" << '\n';
}

void special(const char *)
{
   std::cout << "const char *" << '\n';
}

template<typename T>
void special(const T &)
{
   std::cout << "Single" << '\n';
}

template<typename First, typename... T>
void foo(First&& first, T&&... rest)
{
   std::cout << "Generic + " << sizeof...(T) << '\n';
   special(std::forward<First>(first));
   foo(std::forward<T>(rest)...);
}

int main()
{
    char const* c("foo");
    char a[] = {'a', 'b', 'c', 'd'};
    foo('f', "afas", a, c, 1);
    foo(a);
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380