当仅使用std::variant<T...>
从T...
T 的某些仅向前声明的位置定义的指针/引用时 - 我遇到这个问题,我什至不能使用对此的指针或引用std::variant
- 因为它的基类想要实例化 - 并且它们需要std::is_default_constructible
和其他特征。
参见示例代码:
#include <variant>
struct A;
struct B;
using AB = std::variant<A,B>;
AB* ab();
template <typename T>
void usePointer(T*){}
int main() {
// fails to compile because std::variant gets instantiated here!
usePointer(ab());
}
为了简化示例 - 这就是我所看到的情况:
struct A;
struct B;
template <typename T>
void usePointer(T*){}
template <bool> class SomeBase {};
template <typename T>
struct Some : SomeBase<std::is_default_constructible_v<T>>
{};
Some<A>* someA();
int main() {
// this will not compile - because this
// SomeBase<std::is_default_constructible_v<T>>
// does not compile - because A is unknown
usePointer(someA());
}
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/type_traits: In instantiation of 'constexpr const bool std::is_default_constructible_v<A>':
<source>:12:29: required from 'struct Some<A>'
12 | struct Some : SomeBase<std::is_default_constructible_v<T>>
| ~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:21:23: required from here
21 | usePointer(someA());
| ^
/opt/compiler-explorer/gcc-14.1.0/include/c++/14.1.0/type_traits:3385:54: error: invalid use of incomplete type 'struct A'
3385 | inline constexpr bool is_default_constructible_v = __is_constructible(_Tp);
| ^~~~~~~~~~~~~~~~~~~~~~~
<source>:3:8: note: forward declaration of 'struct A'
3 | struct A;
| ^
Compiler returned: 1
所有 3 个编译器(gcc、clang、msvc)的行为/拒绝方式相同 - 请参阅:https://godbolt.org/z/5cjf1Pv4d
问题:这是正确的吗?对我来说,这确实是一个错误 - 无论是在编译器实现中还是在标准中 - 但是我在标准中找不到任何强制执行此行为的内容。
“有趣”的是,当类模板也被向前声明时 - 一切正常。我的意思是 - 所有编译器都接受此代码:
struct A;
template <typename T>
void usePointer(T*){}
template <typename T> struct Some;
Some<A>* someA();
int main() {
usePointer(someA());
}
这是因为
std::variant
需要实例化 ADL:friend void usePointer(std::variant<A, B>*)
内部可能有一个std::variant
必须使用的声明,因此模板需要实例化(导致错误,因为std::variant
在实例化时需要其类型完整)。解决方法是不使用 ADL:
https://eel.is/c++draft/basic.lookup.argdep#1
usePointer
找不到类成员、块作用域函数或非函数/函数模板。https://eel.is/c++draft/basic.lookup.argdep#3.4
所以
T = std::variant<A, B>*
,U = std::variant<A, B>
https://eel.is/c++draft/basic.lookup.argdep#3.2.sentence-1
(在CWG2857之前,从技术上讲,这不需要实例化该类。但这不是预期的行为,因为
friend
在其他地方隐式实例化模板之前,无法找到模板中的函数,这是一个标准缺陷)因此,该类需要是一个完整的类型才能查看其基础。
https://eel.is/c++draft/temp.inst#2
所以我们必须实例化它。
这也是您的示例
struct A;
能够编译的原因:它不是模板,因此不需要实例化。如果您这样做,那么完整性会影响程序:
...你的程序将成为格式错误的 NDR ( https://eel.is/c++draft/temp.dep.res#temp.point-7.sentence-4 )