c++ - 从使用不完整类型实例化的模板继承

标签 c++ templates inheritance incomplete-type

我有一个如下所示的结构模板:

// 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.hConcreteS.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/

相关文章:

java - 将最终值推迟到子类java

c# - 最小起订量和参数属性继承

c++ - 多态性是否适用于值?或者在按(基)值返回时使用派生类的 move 构造函数

c++ - 将可变迭代器参数转换为值类型的元组

java - 调用哪些重写方法?

drupal - 如何使用不同的 drupal 页面模板?

c++ - 模板参数中的 enable_if 创建模板重新定义错误

c++ - 自定义堆栈分配器,覆盖删除

c++ - 移位运算符忽略数据大小

c++ - 处理 TCP 流