Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unexpected overload called when function is called from variadic template instantiation

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.

like image 793
Griwes Avatar asked Oct 28 '12 13:10

Griwes


1 Answers

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);
}
like image 111
Dietmar Kühl Avatar answered Sep 29 '22 13:09

Dietmar Kühl