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;
}