由类型不完整的类模板的不必要(?)实例化引起的编译器

fab*_*ian 2 c++ templates language-lawyer

如果模板参数是不完整的类,我有一个实例化失败的模板类。

template<class T>
struct Foo
{
    char m_buffer[sizeof(T)]; // breaks on instantiation, if T is an incomplete type
};
Run Code Online (Sandbox Code Playgroud)

仅当此类的前向声明可见时,使用对该类的实例化的引用(具有不完整的类作为模板参数)的一个翻译单元才有效,但如果类模板定义可用,则无效。为什么会出现这种情况?


完整代码

a.hpp

#define DEFINE_FOO_IN_HPP 1 // change this define to 0 to make the code work

template<class T>
struct Foo
#if DEFINE_FOO_IN_HPP
{
    char m_buffer[sizeof(T)]; // breaks on instantiation, if T is an incomplete type
}
#endif
;

struct Bar;

Foo<Bar>& FooBarInstance();
Run Code Online (Sandbox Code Playgroud)

a.cpp

#include "a.hpp"

struct Bar
{
    int m_v;
};

#if !DEFINE_FOO_IN_HPP
template<class T>
struct Foo
{
    char m_buffer[sizeof(T)]; // breaks on instantiation, if T is an incomplete type
};
#endif

Foo<Bar>& FooBarInstance()
{
    static Foo<Bar> val;
    return val;
}
Run Code Online (Sandbox Code Playgroud)

主程序

#include <iostream>

#include "a.hpp"

void f(Foo<Bar>&)
{
    std::cout << "it works\n";
}

int main()
{
    f(FooBarInstance());
}
Run Code Online (Sandbox Code Playgroud)

错误(MSVC)

1>main.cpp
1>...\a.hpp(7,1): error C2027: use of undefined type 'Bar'
1>...\a.hpp(12,8): message : see declaration of 'Bar'
1>...\main.cpp(12,21): message : see reference to class template instantiation 'Foo<Bar>' being compiled
Run Code Online (Sandbox Code Playgroud)

错误(海湾合作委员会)

In file included from /app/main.cpp:3:
a.hpp: In instantiation of 'struct Foo<Bar>':
/app/main.cpp:12:21:   required from here
/app/a.hpp:7:19: error: invalid application of 'sizeof' to incomplete type 'Bar'
    7 |     char m_buffer[sizeof(T)]; // breaks on instantiation, if T is an incomplete type
      |                   ^~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

如果定义a.hpp更改为,GCC 和 MSVC 都不会抱怨

#define DEFINE_FOO_IN_HPP 0
Run Code Online (Sandbox Code Playgroud)

在编译期间只留下类模板的前向声明可见main.cpp

Art*_*yer 7

编译器实例化以检查是否存在可以通过 ADL 找到的可以用对象调用的Foo<Bar>任何友元函数声明。fFoo<Bar>

只需限定调用即可抑制 ADL:

int main()
{
    ::f(FooBarInstance());
    (f)(FooBarInstance());
}
Run Code Online (Sandbox Code Playgroud)

[基本.lookup.argdep]p1

当函数调用中的后缀表达式是unqualified-id时,可能会搜索在通常的非限定查找期间未考虑的其他命名空间,并且在这些命名空间中,命名空间范围的友元函数或函数模板声明([class.friend])不会被搜索可能会发现其他可见的情况。对搜索的这些修改取决于参数的类型(对于模板模板参数,则取决于模板参数的命名空间)。

[basic.lookup.argdep]p2

对于函数调用中的每个参数类型T,需要考虑一组零个或多个关联的命名空间以及一组零个或多个关联的实体(命名空间除外)。命名空间和实体的集合完全由函数参数的类型(以及任何模板模板参数的命名空间)决定。[...]命名空间和实体的集合通过以下方式确定:

  • [...]
  • 如果T是类类型(包括联合),则其关联实体为:类本身;[...]
  • [...]

Foo<Bar>关联实体也是如此, ::(全局命名空间)也是关联命名空间。

[basic.lookup.argdep]p(4.3)

在关联实体集中具有可到达定义的类中声明的任何命名空间范围友元函数或友元函数模板 ([class.friend]) 在其各自的命名空间内都是可见的,即使它们在普通查找期间不可见 ([namespace.memdef ])。

Foo<Bar>所以在 范围内声明的友元函数::应该是可见的。因此,当Foo有可达定义时,需要查找友元函数定义,这就是实例化它的原因。这也是为什么当你有 时就很好DEFINE_FOO_IN_HPP=0,因为不再有一个可到达的定义。