Неоднозначные частичные специализации с 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}>>