Неоднозначные частичные специализации с Clang in С++ 17
template <typename Foo, Foo Part>
struct TSelect {};
enum What {
The
};
template <typename Foo>
struct AnotherOneSelector {
static constexpr Foo Id = Foo::The;
};
template <typename Foo, typename SelectPartType>
struct THelper;
template <typename Foo>
struct THelper<Foo, TSelect<Foo, AnotherOneSelector<Foo>::Id>> {};
template <typename Foo, Foo PartId>
struct THelper<Foo, TSelect<Foo, PartId>> {};
int main() {
THelper<What, TSelect<What, What::The>> t;
}
Этот код компилируется с помощью gcc8.1 с каждым стандартным вариантом (С++ 11, С++ 14, С++ 17), но clang trunk не с С++ 17 (хотя с С++ 14 все в порядке).
Ошибка сообщения:
test.cpp:23:49: error: ambiguous partial specializations of 'THelper<What, TSelect<What, The> >'
THelper<What, TSelect<What, What::The>> t;
^
test.cpp:17:12: note: partial specialization matches [with Foo = What]
struct THelper<Foo, TSelect<Foo, AnotherOneSelector<Foo>::Id>> {};
^
test.cpp:20:12: note: partial specialization matches [with Foo = What, PartId = The]
struct THelper<Foo, TSelect<Foo, PartId>> {};
^
1 error generated.
Какой компилятор прав? Я не видел никаких изменений в специализации шаблонов в С++ 17.
Ответы
Ответ 1
Разница в С++ 17 заключается в том, что вы можете вывести тип параметра non-type из соответствующего аргумента. И Кланг, по-видимому, делает вывод неверным.
В данном случае вы должны синтезировать уникальный тип для Foo
и пытаться вывести Foo
и PartId
в THelper<Foo, TSelect<Foo, PartId>>
против THelper<Unique, TSelect<Unique, AnotherOneSelector<Unique>::Id>>
. Кажется, что Clang рассматривает AnotherOneSelector<Unique>::Id
чтобы иметь отдельный уникальный тип - называть его Unique2
- так что вывод не выполняется на С++ 17, потому что вы вывели конфликтующие типы для Foo
. Обработка не выведенных контекстов, как это, как известно, недоказана, но я уверен, что это означало вывод с использованием аргументированного типа аргумента шаблона, а не оригинала.
Возможны два способа обхода:
- Запретите вывод
Foo
из аргумента non-type путем переноса типа в невыводимый контекст. Например: template <typename Foo, std::remove_const_t<Foo> PartId>
. -
struct THelper<Foo, TSelect<Foo, Foo{AnotherOneSelector<Foo>::Id}>>
преобразование в Foo
в аргументе шаблона, чтобы избежать ложного конфликта: struct THelper<Foo, TSelect<Foo, Foo{AnotherOneSelector<Foo>::Id}>>