Неожиданная перегрузка, вызываемая при вызове функции из экземпляра вариационного шаблона

Предположим, что следующий код:

#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);
}

Результат:

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]

Последний вызов - foo(a), где a - char[4] - вызывает версию, которую я ожидаю - template<unsigned N> void foo(const char (&)[N]). Но почему не создается экземпляр вариационного шаблона foo вызывает foo(const char (&)[N], а вызывает foo(const char *) вместо этого? Если не было перегрузки массива char, этого следовало ожидать - но почему это происходит здесь? Должен ли const First & правильно захватить массив?

Кроме того, что было бы самым простым способом сделать стандартную версию varadic правильной работой с переданными ей массивами?


Как заметил в комментариях Маттиу М., проблема, вероятно, не вызвана вариационными шаблонами, а косвенностью:

#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 *

Он также сказал, что это может быть ошибка компилятора - хотя код дает точно такие же результаты как в Clang 3.2 dev, g++ 4.6 и 4.7.

R. Martinho Fernandes отметил, что изменение типа a в последнем фрагменте на const char a[] приводит к удвоению кода const char *.

Ответ 1

Я думаю, что могу ответить на второй раздел: как это исправить? Я не уверен, получил ли я правильный ответ, потому что случай передачи строкового литерала создает, по-моему, еще неправильный результат. Основная идея исправить заключается в использовании идеальной пересылки, а не в надежде на то, что для T const& выведен правильный тип. Почему использование T const& с массивом приводит к распаду массива, я не совсем понял, хотя.

Использование совершенного вперед, конечно, означает, что функция, выполняющая это, является идеальным совпадением, и некоторые из специализаций действительно делают некоторые преобразования, по крайней мере, добавляя const. Таким образом, необходимо использовать другое имя. В общем, это выглядит так:

#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);
}