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