Какая более специализированная функция шаблона? clang и g++ отличаются тем, что
Во время игры с вариативными шаблонами, следуя этому вопросу SO (примечание: необязательно идти туда, чтобы следовать этому вопросу), я пришел к другому поведению clang (3.8) и g++ (6.1) для следующих перегруженных функций шаблона:
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <template <typename...> class PACK_A,
template <typename...> class PACK_B, typename... Ts1, typename... Ts2>
constexpr bool starts_with(PACK_A<Ts1..., Ts2...>, PACK_B<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(),
pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(),
pack<int>()) << std::endl;
}
Код: http://coliru.stacked-crooked.com/a/b62fa93ea88fa25b
Выход
|---|-----------------------------------------------------------------------------|
| # |starts_with(a, b) | expected | clang (3.8) | g++ (6.1) |
|---|-----------------------------------|-------------|-------------|-------------|
| 1 |a: pack<int, float, double>() | false | false | false |
| |b: pack<float, int, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 2 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, double, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 3 |a: pack<int, float, double>() | false | false | false |
| |b: pack<int, float, int>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 4 |a: pack<int, float, double>() | true | true | false |
| |b: pack<int, float, double>() | | | |
|---|-----------------------------------|-------------|-------------|--------- ---|
| 5 |a: pack<int, float, double>() | true | false | false |
| |b: pack<int>() | | | |
|---|-----------------------------------------------------------------------------|
Последние два случая (4 и 5) находятся под вопросом: не оправдались ли мои ожидания для более специализированного шаблона? и если да, то кто прав в случае 4, clang или g++? (обратите внимание, что код компилируется без какой-либо ошибки или предупреждения для обоих, но с разными результатами).
Пытаясь ответить на это сам, я несколько раз проходил через "более специализированные" правила в спецификации (14.5.6.2 Частичное упорядочение шаблонов функций) и в cppreference - кажется, что более специализированное правило должно дать результат, который я ожидаю (можно ожидать двусмысленности ошибка, если нет, но это тоже не так). Итак, что мне здесь не хватает?
Подождите (1): пожалуйста, не торопитесь и принесите " предпочитают не перегружать шаблоны" Herb Sutter и его проверка методов шаблонов. Это, безусловно, важно, но язык по-прежнему позволяет перегружать шаблоны! (Это действительно важный момент, почему вы предпочитаете не перегружать шаблоны - в некоторых случаях это может смутить двух разных компиляторов или запутать программиста. Но вопрос не в том, использовать его или нет, это: Какое правильное поведение, если вы его используете?).
Подождите (2): не спешите приводить другие возможные решения. Есть точно. Вот два: один с внутренней структурой и другой с внутренними статическими методами. Оба являются подходящими решениями, как работающими, так и ожидаемыми, однако вопрос, связанный с поведением вышеперечисленного шаблона, по-прежнему остается.
Ответы
Ответ 1
Как отметил Холт, стандарт очень строг, когда речь идет о выводе параметров вариационных шаблонов:
14.8.2.5/9
Если P имеет форму, содержащую T или i, то каждый аргумент Pi соответствующий список аргументов шаблона P сравнивается с соответствующим аргумент Ai соответствующего списка аргументов шаблона A. Если Список аргументов шаблона P содержит расширение пакета, которое не является последний аргумент шаблона, весь список аргументов шаблона является не выводимый контекст. Если Pi является расширением пакета, то шаблон Pi сравнивается с каждым оставшимся аргументом в списке аргументов шаблона of A. Каждое сравнение выводит аргументы шаблона для последующих позиции в пакетах шаблонов параметров, расширенных на Pi.
Это интерпретируется T.C. означало бы, что Ts1...
можно вывести из второго аргумента, но он не оставляет места для вычета Ts2...
. Поскольку такой видимый clang был бы здесь и gcc был бы неправильным... Перегрузка должна быть выбрана , только если второй параметр будет содержать точно такие же параметры шаблона, например:
starts_with(pack<int, float, double>(), pack<int, float, double>())
Еще пример 5. не выполняет это требование и не позволяет компилятору выбирать перегрузку.
Ответ 2
только информация: не ответ. Это ответ на вопрос в комментариях:
на gcc5.3, делая следующее небольшое изменение, побуждает его к получению ожидаемых результатов или, по крайней мере, тех же результатов, что и clang.
[email protected]:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with(a, b) {
return false;
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts1..., Ts2...>, pack<Ts1...>) {
return true;
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}
[email protected]:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
false
[email protected]:~$ g++ --version
g++ (Ubuntu 5.3.1-14ubuntu2.1) 5.3.1 20160413
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
[email protected]:~$
а для записи изменение программы для оценки всех пакетов в выведенных контекстах дает успех на обеих платформах:
[email protected]:~$ cat nod.cpp
#include <iostream>
using namespace std;
template <class... Ts>
struct pack { };
template <class a, class b>
constexpr bool starts_with_impl(a, b) {
return false;
}
template<typename...LRest>
constexpr bool starts_with_impl(pack<LRest...>, pack<>)
{
return true;
}
template<typename First, typename...LRest, typename...RRest>
constexpr bool starts_with_impl(pack<First, LRest...>, pack<First, RRest...>)
{
return starts_with_impl(pack<LRest...>(), pack<RRest...>());
}
template <typename... Ts1, typename... Ts2 >
constexpr bool starts_with(pack<Ts2...> p1, pack<Ts1...> p2) {
return starts_with_impl(p1, p2);
}
int main() {
std::cout << std::boolalpha;
std::cout << starts_with(pack<int, float, double>(), pack<float, int, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, int>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int, float, double>()) << std::endl;
std::cout << starts_with(pack<int, float, double>(), pack<int>()) << std::endl;
}
[email protected]:~$ g++ -std=c++14 nod.cpp && ./a.out
false
false
false
true
true
Кредит W.F. для руководства меня в этом направлении.