我有一个如下所示的结构模板:
// S.h
#pragma once
#include <vector>
template <typename T>
struct S
{
std::vector<T*> ts;
virtual ~S() { for (auto* t : ts) t->foo(); }
void attach(T& t) { this->ts.push_back(&t); }
};
然后,我继承一个非模板结构,ConcreteS
,来自S<A>
; struct A
此时还不完整,因为我仅在中声明它
ConcreteS.h
:
// ConcreteS.h
#pragma once
#include "S.h"
struct A;
struct ConcreteS : public S<A>
// struct A incomplete here ^
{
ConcreteS();
~ConcreteS() override;
};
我包括A.h
在ConcreteS.cpp
定义struct A
可见的
执行ConcreteS
的析构函数:
// ConcreteS.cpp
#include "ConcreteS.h"
#include "A.h"
#include <cstdio>
ConcreteS::ConcreteS() { std::puts("ConcreteS()"); }
ConcreteS::~ConcreteS() { std::puts("~ConcreteS()"); }
最后,我实例化ConcreteS
在功能main
:
// main.cpp
#include "ConcreteS.h"
int main()
{
ConcreteS concreteS{};
}
上面的代码可以正常编译(并运行):
- 海湾合作委员会11.3.0;
- Clang 14.0.0。
输出为:
ConcreteS()
~ConcreteS()
但无法编译:
- Visual Studio 2019(编译器:MSVC 19.29.30133.0);
- Visual Studio 2013(编译器:MSVC 18.0.40629.0)。
这是 VS13 的错误消息(VS19 的错误消息类似):
Microsoft (R) Build Engine version 12.0.40629.0
[Microsoft .NET Framework, version 4.0.30319.42000]
Copyright (C) Microsoft Corporation. All rights reserved.
Checking Build System
Building Custom Rule <proj_path>/CMakeLists.txt
cl : Command line warning D9002: ignoring unknown option '/permissive-' [<proj_path>\build_vs13\tmp.vcxproj]
A.cpp
ConcreteS.cpp
main.cpp
<proj_path>\S.h(10): error C2027: use of undefined type 'A' [<proj_path>\build_vs13\tmp.vcxproj]
<proj_path>\ConcreteS.h(5) : see declaration of 'A'
<proj_path>\S.h(10) : while compiling class template member function 'S<A>::~S(void)'
<proj_path>\ConcreteS.h(8) : see reference to class template instantiation 'S<A>' being compiled
<proj_path>\S.h(10): error C2227: left of '->foo' must point to class/struct/union/generic type [<proj_path>\build_vs13\tmp.vcxproj]
Generating Code...
问题:谁是对的? Visual Studio 还是 GCC/Clang?
作为引用,我还发布了 struct A
的声明和定义和我的
CMakeLists.txt
:
// A.h
#pragma once
struct A
{
void foo() const;
};
// A.cpp
#include "A.h"
#include <cstdio>
void A::foo() const { std::puts("A::foo()"); }
# CMakeLists.txt
cmake_minimum_required(VERSION 3.6)
project(tmp)
add_executable(tmp A.cpp A.h ConcreteS.cpp ConcreteS.h S.h main.cpp)
if (MSVC)
target_compile_options(tmp PRIVATE /W4 /WX /permissive-)
else()
target_compile_options(tmp PRIVATE -Wall -Wextra -pedantic -Werror)
endif()
最佳答案
使用S<A>
作为基类导致它被隐式实例化。
通常这不会成为问题,因为您的类(class)不需要 A
在实例化时完成。
但是,S<A>
的析构函数的定义需要A
完整(因为成员(member)访问)。这通常也不是问题,因为成员函数的定义通常不会通过类模板专门化的隐式实例化来隐式实例化,但仅当它们在需要存在定义的上下文中或在析构函数的情况下使用时它可能会被调用。
但是,你的析构函数是 virtual
。对于 virtual
特别是成员函数,未指定它们是否通过包含类的隐式实例化来实例化。 ([temp.inst]/11)
因此,实现可能会也可能不会选择实例化 S<A>::~S<A>
在 main.cpp
的翻译单元中。如果是这样,程序将无法编译,因为定义中的成员访问对于不完整的类型来说格式不正确。
换句话说,程序是否有效以及所有提到的编译器的行为是否符合标准是未指定的。
如果删除 virtual
(和 override
)在析构函数上是 S<A>
的唯一实例允许的析构函数将在 ConcreteS.cpp
中翻译单位在哪里A
已完成且实例化有效。这样程序就有效了,并且也应该在 MSVC 下编译。
关于c++ - 从使用不完整类型实例化的模板继承,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72178357/