G++ и clang++ различное поведение с указателем на функции вариационного шаблона

Другой, кто прямо между g++ и clang++? вопрос для стандартных гуру С++.

Код следующий

template <typename ...>
struct bar
 { };

template <typename ... Ts>
void foo (bar<Ts...> const &)
 { }

int main ()
 {
   foo<int>(bar<int, long>{});     // g++ and clang++ compile

   (*(&foo<int>))(bar<int, long>{});  // g++ and clang++ give error

   (&foo<int>)(bar<int, long>{});  // clang++ compiles; g++ gives error
 }

Функция шаблона foo() принимает параметр вариационного шаблона bar.

Первый вызов

   foo<int>(bar<int, long>{});     // g++ and clang++ compile

работает как для clang++ ang g++.

Если я правильно понял, foo<int> высвечивается только первый параметр шаблона, и это не завершает список параметров Ts.... Таким образом, компилятор просматривает аргумент (объект bar<int, long>) и выводит полный список.

Второй вызов отличается

     (*(&foo<int>))(bar<int, long>{});  // g++ and clang++ give error

Если я правильно понимаю, с (&foo<int>) мы получаем указатель на экземпляр foo, где Ts... - это точно int (не только первый тип списка, но и весь список) и разыменовывая его (< *(&foo<int>)) и вызывая его с неправильным аргументом (объект bar<int, long>), мы получаем (clang++ и g++) ошибку компиляции.

До сих пор так хорошо.

Проблема возникает при третьем вызове

   (&foo<int>)(bar<int, long>{});  // clang++ compiles; g++ gives error

что я был убежден (может быть, я был неправ) эквивалентен второй (мы фиксируем все типы шаблонов в Ts..., тогда мы вызываем функцию с неправильным параметром), но g++ кажется соглашающимся (и дает ошибку), где clang++ не согласен (и скомпилировать без проблем).

Вопрос, как обычно, таков: кто прав?

Ответ 1

Рассмотрим более простой случай:

template <class A, class B>void foo(B) {};

int main()
{
    (&foo<char>)(1);
}

clang++ компилирует это, а g++ терпит неудачу со следующим сообщением:

error: адрес перегруженной функции без информации контекстного типа

Такое же сообщение дается, например, эта программа:

void moo(int) {};
void moo(int*){};

int main()
{
    &moo != nullptr;
}

По-видимому, цель состоит в том, чтобы ссылаться на [over.over], в котором говорится об использовании адреса перегруженной функции. Это место в стандарте указывает, в каких контекстах может использоваться перегруженное имя функции (RHS присвоения, аргумент в вызове функции и т.д.). Однако он говорит

Перегруженное имя функции не должно использоваться без аргументов в других контекстах, кроме перечисленных. (акцент мой)

Теперь, foo в (&foo<char>)(1) используется без аргументов? g++, похоже, тонет. Однако он с радостью компилирует

(&moo)(1);

из второго примера. Поэтому у нас есть хотя бы некоторая несогласованность. Те же правила регулируют выбор адреса шаблона функции и перегруженного набора, поэтому (&foo<char>)(1) и (&moo)(1) должны быть либо действительными, либо оба недопустимыми. Сам стандарт, по-видимому, указывает, что они должны быть действительными:

Перегруженному имени функции может предшествовать оператор и [[]] [Примечание: любой избыточный набор круглых скобок, окружающих перегруженное имя функции, игнорируется (5.1). - конечная нота]