在类特化方面,clang/gcc存在不一致性

9

在尝试为 C++17 中的结构化绑定专门化自定义类的 tuple_size/tuple_element 时,我遇到了这个问题。

下面的代码在 GCC 中可以编译,但在 clang 中无法编译(两个版本都是 trunk 版本,请参见下面的链接)。

#include <type_traits>

template<typename T, typename... Ts>
using sfinae_t = T;

template<typename T, bool... Bs>
using sfinae_v_t = sfinae_t<T, typename std::enable_if<Bs>::type...>;

template <typename T>
struct Test;

template <typename T>
struct Test<sfinae_v_t<T, std::is_integral_v<T>>> {};

void f() {
    Test<int> t;
}

https://godbolt.org/z/ztuRSq

这是clang提供的错误信息:

<source>:13:8: error: class template partial specialization does not specialize any template argument; to define the primary template, remove the template argument list

struct Test<sfinae_v_t<T, std::is_integral<T>::value>> {};

       ^   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

1 error generated.

Compiler returned: 1

这是编译器中的一个错误吗?还是上述代码引发了一些未定义行为?

3
可以更进一步简化 even more - Evg
3
ICC和MSVC都无法编译。 - ChrisMM
1
就我所知,如果我没有完全弄错的话,这应该是不正确的(出于与 此处 相同的原因,那也是不正确的)。 - Max Langhof
谢谢你们两个! - n314159
1
由于我们在引用标准,我添加了“语言律师”标签。 - Guillaume Racicot
显示剩余6条评论
1个回答

3
在下面我所说的(在旧帖下面)应该在一定程度上是真实的,但这个实际上的问题是,SFINAE被错误地使用了,因此我不太确定这是否是gcc中的bug。
别名声明必须始终成功,您不能在那里使用SFINAE,因为它不是类或函数声明或特化(这是有意义的,因为您不能专门指定别名)。如果别名声明不成功,则程序不符合规范。因此,编译器可以假设,在强制实例化这样的模板之前,它永远不会出现别名声明不成功的情况。
因此,对于编译器来说,认为sfinae_v_t<T,...>始终是T是完全可以接受的,因为当程序没有不符合规范时,就会发生这种情况。因此,它将看到,在程序没有不符合规范的情况下,部分特化不会专门化,因此它会告诉您这是不符合规范的。(这就是clang所做的)。
我不认为编译器被迫这样做。如果它没有这样做,只是认为“好的,sfinae_v_t是某种类型,随便。”,那么这不是一个明显的重新声明。所以我认为在我们实例化它们之前,不抛出错误是没有问题的。
但是当我们实例化它时,应该有一个重新声明的问题或由于std::enable_if而导致程序不合法的问题,这取决于模板参数。GCC应该至少捕捉其中之一,但是没有。
这也绝对不适用于没有std::enable_if的更简单的示例。所以我仍然认为这是GCC的一个错误,但我已经足够混乱了,不能再肯定地说了。我只是想说,有人应该将其报告为错误,并让gcc的人们考虑一下。
旧帖子
这是gcc的一个bug。标准为我们提供了将类模板转换为函数模板的规则。如果一个类模板的函数在部分函数模板排序中位于另一个类模板的函数之前,则该类模板更为特殊。
我创建了这里的函数,现在gcc声称调用它们是模棱两可的,因此它也必须说类模板同样被指定。
注:仔细阅读标准,我脑海中的编译器与clang一致。

sfinae_v_t<T, std::is_integral_v<T>>sfinae_v_t<T, !std::is_integral_v<T>> 被视为相同类型吗?从语义上讲,它们并不相同。 - ofo
@GuillaumeRacicot 可能是这样,但我想了解为什么。例如,标准还:“在声明部分特化时无法检查依赖名称,但将在替换到部分特化时进行检查。” 这是否意味着在将T替换为部分特化时才决定它们是否为相同类型,因为sfinae_v_t<T>依赖于T? 在这种情况下,它们不会相同,因为其中一个将是非法的。 - ofo
@ofo 我必须说,我不确定。即使考虑这两个东西也有点让人困惑,因为其中一个永远不会成为一种类型,并且在非模板上下文中同时使用它们将由于 enable_if_t 导致编译错误。我对标准的最佳解读是,它并不重要它们是否相同。对于部分排序,我们将始终将一个函数的模板参数形式与另一个函数的模板参数形式进行比较(即 int 已经被替换),然后其中一个具有真实类型,因此我们不必抽象地比较它们。 - n314159
1
深入挖掘后,我从这里找到了这个。SFINAE应该可以与模板别名很好地配合使用,否则template<bool B, typename T> enable_if_t = typename enable_if<B, T>::type;也不会起作用。我将继续对gcc提出错误报告,但不确定gcc是否有错。谢谢。 - ofo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接