我想写一个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中,尽管A
类在功能print
中不完整,但程序仍然可以打印0
。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函数模板的专门化…在一个翻译单元中可以有多个实例化点,并且除了上面描述的实例化点之外,
对于任何这样的专门化,在…翻译单位…,点后…[end of]翻译单元也被认为是实例化点,
如果两个不同的实例化点根据单定义规则赋予模板专门化不同的含义,则该程序是病态的,不需要诊断。
已经正确地指出,这可能导致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
。(参见这里)。