从不完整类型实例化的模板继承

Inheriting from template instanciated with 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); }
};

然后,我从S<A>继承了一个非模板结构ConcreteSstruct A 在这一点上是不完整的,因为我 only 向前声明它 ConcreteS.h:

// ConcreteS.h
#pragma once

#include "S.h"

struct A;
struct ConcreteS : public S<A>
// struct A incomplete here ^
{
    ConcreteS(); 
    ~ConcreteS() override; 
};

我在 ConcreteS.cpp 中包含 A.h 以使 struct A 的定义可见 ConcreteS 的析构函数的实现:

// ConcreteS.cpp
#include "ConcreteS.h"
#include "A.h"

#include <cstdio>

ConcreteS::ConcreteS() { std::puts("ConcreteS()"); }
ConcreteS::~ConcreteS() { std::puts("~ConcreteS()"); }

最后,我在函数 main:

中实例化 ConcreteS
// main.cpp
#include "ConcreteS.h"

int main()
{
    ConcreteS concreteS{};
}

以上代码编译(并运行)正常:

输出为:

ConcreteS()
~ConcreteS()

但编译失败:

这是来自 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?

为了参考,我也post声明和定义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 导致它被隐式实例化。

通常这不会成为问题,因为您的 class 在实例化时不需要 A 完成。

但是,S<A> 的析构函数定义要求 A 完整(因为成员访问)。这通常也不是问题,因为成员函数的定义通常不会用 class 模板特化的隐式实例化来隐式实例化,但只有当它们用于需要定义存在的上下文中或在这种情况下析构函数可能被调用.

然而,你的析构函数是virtual。特别是对于 virtual 成员函数,未指定它们是否使用包含 class 的隐式实例化进行实例化。 ([temp.inst]/11)

因此,实现可能会或可能不会选择在 main.cpp 的翻译单元中实例化 S<A>::~S<A>。如果是这样,程序将无法编译,因为定义中的成员访问是 ill-formed 不完整类型。

换句话说,未指定程序是否有效以及所有提到的编译器的行为都符合标准。

如果删除析构函数上的 virtual(和 override),则允许的 S<A> 析构函数的唯一实例将在 ConcreteS.cpp 翻译单元中其中 A 是完整的并且实例化有效。该程序然后是有效的,它也应该在 MSVC 下编译。