is_base_of SFINAE 开启 class
is_base_of SFINAE toggle on class
我正在开发用于声明式 UI 构建的模板工具包。我按预期在 Windows 上编译了模板,但 GCC/clang 编译器对我的 SFINAE 工作提出了质疑。
我正在使用 Qt 作为基础,我希望在包装器结构中包含 QLayout 和 QWidget 项,同时根据它们所代表的对象切换 on/off 包装器的功能。
它们都派生自 QObject,它提供了大量的共同点,但我想包括它们的扩展功能。
在 MSVC 上,下面的效果非常好
// let cls be the input subclass of QWidget or
// QLayout which is passed to the owning wrapper template
template<typename = std::enable_if<std::is_base_of<QWidget, cls>::value == true>>
Wrapper &style() {
cast()->setStyleSheet(stylesheet);
return *this;
}
然而GCC/clang不同意。我试过使用类似于:
template<typename Empty = void,
typename = typename std::enable_if<
std::is_base_of<QWidget, cls>::value, Empty
>::type
>
Wrapper &style(const QString &stylesheet) {
cast()->setStyleSheet(stylesheet);
return *this;
}
但是仍然得到一个错误,指出 QLayout subclasses 没有成员 setStyleSheet
。虽然它没有,但我希望让编译器像在 MSVC 中那样完全跳过该函数。
我假设我对 SFINAE 系统有误解,需要调整我的模板声明,或者只是吸收它并拆分两个项目并重复代码。
编辑:
问题重现:
https://gcc.godbolt.org/z/oGzsKhrn4
最高位只是 Qt 对象的最小 reprs,它代表了我所追求的相同的基本结构,你几乎可以忽略它们。
宏是为了简化每个 class 所需的模板。如果不出意外,我总是可以将其分解并添加额外的宏来填充 QWidget 与 QLayout 功能,我只是喜欢 Windows.
的干净程度
在您的示例中,style
函数体的行为不依赖于其模板参数,编译器可以检查其正确性。我猜这是 [temp.res#general]:
中的标准允许的
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,
...
但是,您可以像以前一样将 QVBoxLayout
设置为模板默认参数(而不是 Empty
),但也要使正文依赖于此参数:
template<typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}
但在这种情况下我们遇到了问题。有些用户可能不小心传递了这个参数,SFINAE 会被破坏,所以我提出以下修复。添加参数包作为第一个函数模板参数。它会“吃掉”所有意外传递的参数,因此用户将无法覆盖默认参数:
template<typename..., typename QVBoxLayout_ = QVBoxLayout...
当然,您可以添加断言,表明用户不会无意中尝试覆盖这些参数。总结:
template<typename... ParameterGuard_,
typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
static_assert(sizeof...(ParameterGuard_) == 0,
"This function template is not intended to accept template parameters");
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}
我正在开发用于声明式 UI 构建的模板工具包。我按预期在 Windows 上编译了模板,但 GCC/clang 编译器对我的 SFINAE 工作提出了质疑。
我正在使用 Qt 作为基础,我希望在包装器结构中包含 QLayout 和 QWidget 项,同时根据它们所代表的对象切换 on/off 包装器的功能。
它们都派生自 QObject,它提供了大量的共同点,但我想包括它们的扩展功能。
在 MSVC 上,下面的效果非常好
// let cls be the input subclass of QWidget or
// QLayout which is passed to the owning wrapper template
template<typename = std::enable_if<std::is_base_of<QWidget, cls>::value == true>>
Wrapper &style() {
cast()->setStyleSheet(stylesheet);
return *this;
}
然而GCC/clang不同意。我试过使用类似于:
template<typename Empty = void,
typename = typename std::enable_if<
std::is_base_of<QWidget, cls>::value, Empty
>::type
>
Wrapper &style(const QString &stylesheet) {
cast()->setStyleSheet(stylesheet);
return *this;
}
但是仍然得到一个错误,指出 QLayout subclasses 没有成员 setStyleSheet
。虽然它没有,但我希望让编译器像在 MSVC 中那样完全跳过该函数。
我假设我对 SFINAE 系统有误解,需要调整我的模板声明,或者只是吸收它并拆分两个项目并重复代码。
编辑:
问题重现: https://gcc.godbolt.org/z/oGzsKhrn4
最高位只是 Qt 对象的最小 reprs,它代表了我所追求的相同的基本结构,你几乎可以忽略它们。
宏是为了简化每个 class 所需的模板。如果不出意外,我总是可以将其分解并添加额外的宏来填充 QWidget 与 QLayout 功能,我只是喜欢 Windows.
的干净程度在您的示例中,style
函数体的行为不依赖于其模板参数,编译器可以检查其正确性。我猜这是 [temp.res#general]:
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,
...
但是,您可以像以前一样将 QVBoxLayout
设置为模板默认参数(而不是 Empty
),但也要使正文依赖于此参数:
template<typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}
但在这种情况下我们遇到了问题。有些用户可能不小心传递了这个参数,SFINAE 会被破坏,所以我提出以下修复。添加参数包作为第一个函数模板参数。它会“吃掉”所有意外传递的参数,因此用户将无法覆盖默认参数:
template<typename..., typename QVBoxLayout_ = QVBoxLayout...
当然,您可以添加断言,表明用户不会无意中尝试覆盖这些参数。总结:
template<typename... ParameterGuard_,
typename QVBoxLayout_ = QVBoxLayout,
typename = typename std::enable_if<
std::is_base_of<QWidget, QVBoxLayout_>::value, void
>::type
>
VLayout &style(const QString &stylesheet) {
static_assert(sizeof...(ParameterGuard_) == 0,
"This function template is not intended to accept template parameters");
QVBoxLayout_::cast()->setStyleSheet(stylesheet); // "shouldn't matter"
return *this;
}