我想编写一个 C++ 函数来检查其模板参数类是否不完整,因此只有类声明可用,但没有所有类成员的完整定义。
我的功能
incomplete()
与一些演示程序一起看起来如下:
#include <type_traits>
#include <iostream>
template <typename T, typename V = void> constexpr bool is_incomplete = true;
template <typename T> constexpr bool is_incomplete<T, std::enable_if_t<sizeof(T)>> = false;
template <typename T> constexpr bool incomplete() { return is_incomplete<T>; }
struct A;
void print() { std::cout << incomplete<A>(); }
struct A {}; //this line affects GCC
int main()
{
print();
}
它在 Clang 打印
1
中运行良好,但在 GCC 中,程序打印 0
,尽管 A
类在函数 print
中不完整。
https://gcc.godbolt.org/z/qWW3hqbEv
是 GCC 错误还是我的程序有问题?
哪个编译器是正确的目前尚未确定。这是CWG 第 1845 期。
13.8.4.1 [temp.point]的当前措辞没有定义变量模板特化的实例化点。据推测,将第 1 段和第 8 段中对“类模板的静态数据成员”的引用替换为“变量模板”就足够了。
鉴于该问题仍处于起草阶段(且尚无规范性措辞,但为最新可用草案),因此仍未得到解决。目前尚不清楚变量模板是否可以具有多个实例化点(尽管问题报告者建议的方向很明确)。
对于具有多个实例化点的实体,翻译单元的末尾也是一个。此外,如果两点在模板的定义上存在分歧,那么这就是 ODR 违规,简单明了。 GCC 似乎提供了两点,所以你看到了结果。我倾向于同意 GCC 的观点。变量模板在很多方面都是类模板的静态数据成员的简写,并且静态数据成员确实具有多个实例化点。
无论哪种方式,这都是在玩鼻恶魔的风险。如果该状态可以在 TU 中更改(甚至可能在整个程序中更改),则实际上不可能可靠地检查类型的完整性。
如果某个类在某个时刻变得完整,那么发现一个类是否不完整将违反一个定义规则(ODR),因此该问题应该没有有效的解决方案。
我建议的更多来自 https://eel.is/c++draft/temp.point 的引用:
1 对于函数模板特化...实例化点...紧跟在引用特化的命名空间范围声明或定义之后。
7 函数模板的特化...在翻译单元内可能有多个实例化点,除了上面描述的实例化点之外,
对于在...翻译单元...内具有实例化点的任何此类专业化,...[结束]翻译单元之后的点也被视为实例化点,
如果根据单定义规则,两个不同的实例化点赋予模板特化不同的含义,则该程序是格式错误的,无需诊断。
已经正确指出,这可能会导致 ODR 违规,从而导致未定义的行为。为了使这个问题更加具体,请考虑下面的程序,其运行时行为取决于
static_assert
是否存在!显然这真的很恶心。
// is_incomplete is defined in the OP.
struct foo;
template <typename T>
void f() {
if (is_incomplete<T>)
puts("hello");
else
puts("goodbye");
}
int main() {
f<foo>();
}
static_assert(is_incomplete<foo>);
struct foo{};
使用 GCC 13.1 和 Clang 16.0 编译时,程序显示
hello
。但是,如果删除 static_assert
,则输出为 goodbye
。
(参见此处。)