Подтвердить что ты не робот

Какая более специализированная функция шаблона? 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): не спешите приводить другие возможные решения. Есть точно. Вот два: один с внутренней структурой и другой с внутренними статическими методами. Оба являются подходящими решениями, как работающими, так и ожидаемыми, однако вопрос, связанный с поведением вышеперечисленного шаблона, по-прежнему остается.

4b9b3361

Ответ 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. для руководства меня в этом направлении.