在模板中使用不完整的类型
Use of incomplete types in templates
如果在模板实例化时类型是完整的,那么在模板中使用不完整的类型是否合法?
如下
#include <iostream>
struct bar;
template <typename T>
struct foo {
foo(bar* b) : b(b) {
}
void frobnicate() {
b->frobnicate();
}
T val_;
bar* b;
};
struct bar {
void frobnicate() {
std::cout << "foo\n";
}
};
int main() {
bar b;
foo<int> f(&b);
f.frobnicate();
return 0;
}
Visual Studio 毫无怨言地编译了上面的内容。 GCC 发出警告 invalid use of incomplete type 'struct bar'
但编译。 member access into incomplete type 'bar'
.
出现 Clang 错误
虽然 MSVC 和 GCC 的行为也符合标准,但 Clang 报告错误(与警告或保持沉默相反)是正确的。有关详细信息,请参阅@HolyBlackCat 的回答。
您发布的代码是 ill-formed NDR。不过,你想做的事情是可行的。
您可以像 non-template class 一样延迟模板成员函数的定义。很像 non-template classes,只要这些要求 bar
是完整类型的定义仅在 bar
完成后发生,一切都很好。
唯一的问题是您需要将方法显式标记为内联以避免 multi-TU 程序中的 ODR 违规,因为定义几乎肯定会在 header.[=13= 中]
#include <iostream>
struct bar;
template <typename T>
struct foo {
foo(bar* b) : b(b) {
}
inline void frobnicate();
T val_;
bar* b;
};
struct bar {
void frobnicate() {
std::cout << "foo\n";
}
};
template <typename T>
void foo<T>::frobnicate() {
b->frobnicate();
}
int main() {
bar b;
foo<int> f(&b);
f.frobnicate();
return 0;
}
代码格式错误,无需诊断。
[temp.res.general]/6.4
The validity of a template may be checked prior to any instantiation.
The program is ill-formed, no diagnostic required, if:
...
— a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, ...
如果你绝对不能在模板前定义bar
,有一个解决方法:你可以在模板参数上引入一个人为的依赖。
template <typename T, typename, typename...>
struct dependent_type {using type = T;};
template <typename T, typename P0, typename ...P>
using dependent_type_t = typename dependent_type<T, P0, P...>::type;
然后使用 dependent_type_t<bar, T>
而不是 bar
。
如果您想使用前向声明作为模板参数来自定义模板,您可以这样做(没有警告或错误):
template <typename T, typename = T>
class print_name;
所以当你进行部分特化时,你使用第二个非特化模板参数进行调用:
struct john;
template <typename T>
class print_name<john, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.name << std::endl;
}
};
在这种情况下 T
并非不完整。但是当你实例化的时候print_name<john>
,SFINAE会踢。
这是一个完整的例子:
#include <iostream>
template <typename T, typename = T>
class print_name;
struct john;
template <typename T>
class print_name<john, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.name << std::endl;
}
};
struct slim;
template <typename T>
class print_name<slim, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.myName << std::endl;
}
};
#include <string>
struct john
{
std::string name;
};
struct slim
{
std::string myName;
};
int main()
{
print_name<john>{}(john{"John Cena"});
print_name<slim>{}(slim{"Slim Shady"});
return 0;
}
如果在模板实例化时类型是完整的,那么在模板中使用不完整的类型是否合法?
如下
#include <iostream>
struct bar;
template <typename T>
struct foo {
foo(bar* b) : b(b) {
}
void frobnicate() {
b->frobnicate();
}
T val_;
bar* b;
};
struct bar {
void frobnicate() {
std::cout << "foo\n";
}
};
int main() {
bar b;
foo<int> f(&b);
f.frobnicate();
return 0;
}
Visual Studio 毫无怨言地编译了上面的内容。 GCC 发出警告 invalid use of incomplete type 'struct bar'
但编译。 member access into incomplete type 'bar'
.
虽然 MSVC 和 GCC 的行为也符合标准,但 Clang 报告错误(与警告或保持沉默相反)是正确的。有关详细信息,请参阅@HolyBlackCat 的回答。
您发布的代码是 ill-formed NDR。不过,你想做的事情是可行的。
您可以像 non-template class 一样延迟模板成员函数的定义。很像 non-template classes,只要这些要求 bar
是完整类型的定义仅在 bar
完成后发生,一切都很好。
唯一的问题是您需要将方法显式标记为内联以避免 multi-TU 程序中的 ODR 违规,因为定义几乎肯定会在 header.[=13= 中]
#include <iostream>
struct bar;
template <typename T>
struct foo {
foo(bar* b) : b(b) {
}
inline void frobnicate();
T val_;
bar* b;
};
struct bar {
void frobnicate() {
std::cout << "foo\n";
}
};
template <typename T>
void foo<T>::frobnicate() {
b->frobnicate();
}
int main() {
bar b;
foo<int> f(&b);
f.frobnicate();
return 0;
}
代码格式错误,无需诊断。
[temp.res.general]/6.4
The validity of a template may be checked prior to any instantiation.
The program is ill-formed, no diagnostic required, if:
...
— a hypothetical instantiation of a template immediately following its definition would be ill-formed due to a construct that does not depend on a template parameter, ...
如果你绝对不能在模板前定义bar
,有一个解决方法:你可以在模板参数上引入一个人为的依赖。
template <typename T, typename, typename...>
struct dependent_type {using type = T;};
template <typename T, typename P0, typename ...P>
using dependent_type_t = typename dependent_type<T, P0, P...>::type;
然后使用 dependent_type_t<bar, T>
而不是 bar
。
如果您想使用前向声明作为模板参数来自定义模板,您可以这样做(没有警告或错误):
template <typename T, typename = T>
class print_name;
所以当你进行部分特化时,你使用第二个非特化模板参数进行调用:
struct john;
template <typename T>
class print_name<john, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.name << std::endl;
}
};
在这种情况下 T
并非不完整。但是当你实例化的时候print_name<john>
,SFINAE会踢。
这是一个完整的例子:
#include <iostream>
template <typename T, typename = T>
class print_name;
struct john;
template <typename T>
class print_name<john, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.name << std::endl;
}
};
struct slim;
template <typename T>
class print_name<slim, T>
{
public:
void operator()(const T& f) const
{
std::cout << f.myName << std::endl;
}
};
#include <string>
struct john
{
std::string name;
};
struct slim
{
std::string myName;
};
int main()
{
print_name<john>{}(john{"John Cena"});
print_name<slim>{}(slim{"Slim Shady"});
return 0;
}