C++17 中 Clang 的不明确部分特化

Ambiguous partial specializations with Clang in C++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;
}

此代码使用带有每个标准选项(c++11、c++14、c++17)的 gcc8.1 进行编译,但 clang trunk 不使用 c++17 (although with c++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.

哪个编译器是正确的?我没有看到模板有任何变化 专注于 C++17。

C++17 的不同之处在于,您可以从相应的参数中推断出非类型参数的类型。而 Clang 显然在推论上做错了。

与此处相关,您应该为 Foo 合成一个唯一类型,并尝试针对 THelper<Unique, TSelect<Unique, AnotherOneSelector<Unique>::Id>> 推导 THelper<Foo, TSelect<Foo, PartId>> 中的 FooPartId ].似乎正在发生的事情是 Clang 将 AnotherOneSelector<Unique>::Id 视为具有一些单独的唯一类型 - 称之为 Unique2 - 因此推导在 C++17 中失败,因为你为 Foo 推导了冲突的类型.像这样处理非推导上下文是出了名的未指定,但我很确定它是为了使用转换后的模板参数的类型而不是原始类型来推导。

两种可能的解决方法是:

  • 通过将类型包装到非推导上下文中,禁止从非类型参数推导 Foo。例如:template <typename Foo, std::remove_const_t<Foo> PartId>.
  • 在模板参数中强制转换为 Foo 以避免虚假冲突:struct THelper<Foo, TSelect<Foo, Foo{AnotherOneSelector<Foo>::Id}>>