【问题标题】:is_base_of SFINAE toggle on classis_base_of SFINAE 切换类
【发布时间】:2022-01-04 02:45:16
【问题描述】:

我正在开发用于声明式 UI 构建的模板工具包。我按预期在 Windows 上编译了模板,但 GCC/clang 编译器对我的 SFINAE 工作提出了问题。

我使用 Qt 作为基础,我希望在包装器结构中包含 QLayout 和 QWidget 项,同时根据它们所代表的对象切换包装器的打开/关闭功能。

它们都源自 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 子类没有成员 setStyleSheet。虽然没有,但我希望编译器能像在 MSVC 中那样完全跳过该函数。

我假设我对 SFINAE 系统有误解,需要调整我的模板声明,或者干脆把它收起来,拆分两个项目并重复代码。

编辑:

问题的再现: https://gcc.godbolt.org/z/oGzsKhrn4

上面的位只是 Qt 对象的最小代表,它代表了我所追求的相同基本结构,你几乎可以忽略它们。

宏是为了简化每个类所需的模板。如果不出意外,我总是可以将它分解并添加额外的宏来填充 QWidget 与 QLayout 功能,我只是喜欢它在 Windows 上的干净程度。

【问题讨论】:

  • 什么是cls?和style()有什么关系?
  • 直到最近,MSVC 还没有正确实现两阶段名称查找。本质上,它将模板视为宏并对其进行解析以找到右大括号;然后在实例化时完全重新解析它们。这是不合格的;符合标准的编译器将在定义点解析和验证大部分模板,并且仅在实例化点推迟查找依赖名称。您可能依赖于 MSVC 的这一缺陷。如需进一步帮助,请准备minimal reproducible example。正如所写,尚不清楚 clscast()stylesheet 是什么。
  • Minimal 对我来说是一个挑战,因为我其他小规模复制的大多数尝试都失败了。我确实设法整理了一些确实会引发错误并更新了问题的东西,但它并不是很小。
  • 呃,宏滥用。您能否尝试提出一个不使用使代码难以理解和理解的宏的minimal reproducible example
  • 公平点 - 我已经解压缩了宏并尝试尽可能减少,同时仍然包括我所追求的全貌。也就是说,我最终只是将宏拆分以避免问题。这是每个包装类的额外代码行,但比再继续旋转要好

标签: c++ templates sfinae


【解决方案1】:

在您的示例中,style 函数体的行为不依赖于其模板参数,编译器可以检查其正确性。我想这是[temp.res#general]中的标准允许的:

程序格式错误,不需要诊断,如果:
...
— 由于不依赖于模板参数的构造,紧随其定义的模板的假设实例化将是格式错误的,
...

但是,您可以像以前一样将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;
}

【讨论】:

  • 太棒了,我明白了!稍作调整(使用VLayout 而不是QVBoxLayout 作为::cast() 的所有者就像一种魅力。所以我需要在模板中添加一个typename VLayout_ = VLayout,然后用它调用演员。
猜你喜欢
  • 2021-05-20
  • 2017-12-16
  • 2016-04-12
  • 1970-01-01
  • 2018-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多