在显式实例化期间,不完整的类型何时可以?
When are incomplete types okay during explicit instantiation?
我正在尝试制作一种自动创建包装对象的包装类:
#include <memory>
#include <type_traits>
template<typename T>
class Foo {
std::unique_ptr<T> _x;
public:
Foo(); // will initialize _x
};
此外,我希望能够对Foo<T>
用户隐藏T
的实现细节(对于 PIMPL 模式(。对于单个翻译单元的示例,假设我有
struct Bar; // to be defined later
extern template class Foo<Bar>;
// or just imagine the code after main() is in a separate translation unit...
int main() {
Foo<Bar> f; // usable even though Bar is incomplete
return 0;
}
// delayed definition of Bar and instantiation of Foo<Bar>:
template<typename T>
Foo<T>::Foo() : _x(std::make_unique<T>()) { }
template class Foo<Bar>;
struct Bar {
// lengthy definition here...
};
这一切都很好用。但是,如果我想要求T
派生自另一个类,编译器会抱怨Bar
不完整:
struct Base {};
template<typename T>
Foo<T>::Foo() : _x(std::make_unique<T>()) {
// error: incomplete type 'Bar' used in type trait expression
static_assert(std::is_base_of<Base, T>::value, "T must inherit from Base");
}
尝试使用static_cast
实现相同检查失败类似:
template<typename T>
Foo<T>::Foo() : _x(std::make_unique<T>()) {
// error: static_cast from 'Bar *' to 'Base *', which are not related by inheritance, is not allowed
// note: 'Bar' is incomplete
(void)static_cast<Base*>((T*)nullptr);
}
但是,似乎如果我添加另一个级别的函数模板,我可以完成这项工作:
template<typename Base, typename T>
void RequireIsBaseOf() {
static_assert(std::is_base_of<Base, T>::value, "T must inherit from Base");
}
// seems to work as expected
template<typename T>
Foo<T>::Foo() : _x((RequireIsBaseOf<Base, T>(), std::make_unique<T>())) { }
请注意,尽管结构相似,但即使以下情况仍会导致不完整的类型错误:
// error: incomplete type 'Bar' used in type trait expression
template<typename T>
Foo<T>::Foo() : _x((std::is_base_of<Base, T>::value, std::make_unique<T>())) { }
这是怎么回事?附加功能是否以某种方式延迟了static_assert的检查?有没有一个更干净的解决方案,不涉及添加函数,但仍允许在Bar
的定义之前放置template class Foo<Bar>;
?
版本 1
// #1
// POI for Foo<Bar>: class templates with no dependent types are instantiated at correct scope BEFORE call, with no further lookup
// after first parse
int main() {
Foo<Bar> f; // usable even though Bar is incomplete
return 0;
}
// delayed definition of Bar and instantiation of Foo<Bar>:
struct Base {};
// error: incomplete type 'Bar' used in type trait expression
template<typename T>
Foo<T>::Foo() : _x(std::make_unique<T>()) {
// error: incomplete type 'Bar' used in type trait expression
static_assert(std::is_base_of<Base, T>::value, "T must inherit from Base");
}
// #2
// POI for static_assert: function templates with no dependent types are
// instantiated at correct scope AFTER call, but no further lookup is
// performed, as with class templates without dependent types
// is_base_of forces the compiler to generate a complete type here
template class Foo<Bar>;
struct Bar : private Base {
// lengthy definition here...
};
版本2:
struct Base {};
template<typename Base, typename T>
void RequireIsBaseOf() {
static_assert(std::is_base_of<Base, T>::value, "T must inherit from Base");
}
// seems to work as expected
template<typename T>
Foo<T>::Foo() : _x((RequireIsBaseOf<Base, T>(), std::make_unique<T>())) { }
// #3
// is_base_of does not force any complete type, as so far, only the
// incomplete type of RequiredIsBaseOf is around.
template class Foo<Bar>;
struct Bar : private Base {
// lengthy definition here...
};
// #3
// POI for RequiredIsBaseOf: function templates WITH dependent types are instantiated at correct scope AFTER call, after the second phase of two-phase lookup is performed.
在我看来,这是摩擦: 根据规则,#2 之后的任何点都是允许的 POI(实例化点,编译器放置专用模板代码(。
实际上,大多数通信器将大多数函数模板的实际实例化延迟到翻译单元的末尾。某些实例化不能延迟,包括需要实例化来确定推导的返回类型的情况,以及函数是constexpr并且必须计算以产生常量结果的情况。某些编译器在首次使用内联函数时会实例化内联函数,以可能立即内联调用。这有效地将相应模板专业化的 POI 删除到翻译单元的末尾,这是 C++ 标准允许作为替代 POI(来自C++ 模板,完整指南,第 2 版,14.3.2。实例化点》,第254页(
std::is_base_of 需要一个完整的类型,所以当它没有被RequiredIsBaseOf
隐藏时,它可以作为函数模板部分实例化,is_base_of
可能会导致插入 POI 的编译器尽快发出错误。
正如 t.niese 所指出的,任何可以在 godbolt 上采用-std=c++17
标志的 gcc 版本都可以使用任何一个版本。我的猜测是,gcc 做了晚期 POI 的事情之一,而 clang 使用第一个合法的事情,#2
.使用具有依赖名称的函数模板(当第一次遇到 RequiredIsBaseOf 时,仍然必须填写 T(会强制 clang 对依赖类型进行第二次查找运行,此时已经遇到了Bar
。
不过,我不确定如何实际验证这一点,因此欢迎更精通编译器的人提供任何意见。
- 了解类型是否可调用
- 检查模板中 nullptr 的函数指针,了解任何类型的可调用对象
- 当前不会命中断点。没有调试器目标代码类型的可执行代码与此文件关联
- 键入特征以检查类型是否可从流和 MSVC 读取
- 具有不同类型的可选参数的调用方函数
- 在显式实例化期间,不完整的类型何时可以?
- 哪种类型特征表明该类型是可分配的?(元组,对)
- 为什么 std::vector 允许对其包含的类型使用可抛出的移动构造函数?
- 模板类型是否可选使用?
- 如何在观察器中处理具有不同状态值类型的可观察量
- 如何正确使用带有公共成员变量的类型 boost::可选?
- 检查变量类型是否可迭代?
- 如果包含的类型是可简单复制的类型,则 std::Optional 是否为可平凡复制的类型
- 引用对象的动态类型何时可以更改
- 检测类型何时不需要调用其析构函数
- 类型何时提升为无符号 int
- C++ 模板类/类型,可在最大值和最小值之间进行选择
- c++11 中的类型何时允许内存
- std::引用类型的可选专用化
- 在自由函数中定义的类型,可通过自动外部访问.语言错误或功能