【问题标题】:Constructor SFINAE and inheritence fails in clang构造函数 SFINAE 和继承在 clang 中失败
【发布时间】:2019-11-05 06:46:58
【问题描述】:

以下代码在 GCC 下编译正常,但在 clang 中失败并出现错误:

'Bar'的初始化没有匹配的构造函数

问题似乎是clang认为Foo的模板构造函数被Bar的模板构造函数隐藏或覆盖了。

这是 clang 中的错误还是 GCC 中的非标准化功能?

如何解决这个问题?我无法更改 Foo,因为它是第 3 方。

#include <type_traits>

struct Foo {
    Foo() = default;

    template<typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
    Foo(T& object) {}
};

struct Bar : public Foo {

    using Foo::Foo;

    template<typename T, std::enable_if_t<!std::is_trivially_copyable_v<T>>* = nullptr>
    Bar(T& object) {}
};

int main() {
    int i;
    Bar s{i};
}

https://gcc.godbolt.org/z/etvpvF

【问题讨论】:

  • 您的 Bar 构造函数只接受非平凡的数据类型,并且您传递给它一个整数。我错过了什么吗?
  • 但是我用using Foo::Foo;继承了Foo构造函数。
  • @NutCracker - 从Foo继承的构造函数

标签: c++ gcc clang c++17 sfinae


【解决方案1】:

Clang 是正确的。 [namespace.udecl]/14:

(强调我的)

当 using-declarator 将基类中的声明带入 派生类、成员函数和成员函数模板 派生类覆盖和/或隐藏成员函数和成员 具有相同名称的函数模板,参数类型列表, 基类中的 cv-qualification 和 ref-qualifier(如果有)(而不是 比冲突)。此类隐藏或覆盖的声明被排除在外 来自 using-declarator 引入的一组声明。

这意味着,对于这种情况,来自foo 的继承构造函数模板隐藏在来自Bar 的构造函数模板的查找中。请注意,只有 name、parameter-type-list、cv-qualification 和 ref-qualifier 被认为是隐藏的,即使对于模板也是如此。

【讨论】:

  • 这适用于非构造函数。显然 c'tors 没有 cv 限定符。 C'tors 也...很特别,因为它们不是完全引入的。这确实是一团糟,尤其是考虑到模板的复杂性。
  • @StoryTeller-UnslanderMonica 我尝试将它们更改为非构造函数模板,行为是相同的。看来gcc 是错误的;即使是非构造函数。
  • @StoryTeller-UnslanderMonica 我认为关键在于是否应在名称隐藏中考虑第二个模板参数声明(这是两个构造函数之间唯一不同的部分),即是否应将其视为部分参数类型列表,对吗?
  • @StoryTeller-UnslanderMonica: "This is for non-constructors." 示例还显示了构造函数大小写:" with *"// OK: D2​::​D2(int) hides B1​::​B1(int) and B2​::​B2(int)" .
  • @Jarod42 - 示例一开始是不规范的。正如我所说,这是一团糟。 namespace.udecl/13 中也有问题,其中模板参数列表被正确地考虑用于命名空间的情况。而且我们知道,如果我们使用函数参数而不是模板参数,Clang 知道何时延迟隐藏。这可能值得一个 CWG 问题。似乎还没有与它相关的内容。
【解决方案2】:

添加第二个模板参数和默认参数时,Clang 不会继承构造函数模板。另一方面,在函数的参数列表(C++03 风格)中使用 SFINAE 构造时没有问题:

struct Foo {
    Foo() = default;

    template<typename T>
    Foo(T& object, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr) {}
};

struct Bar : public Foo {
    using Foo::Foo;

    template<typename T>
    Bar(T& object, std::enable_if_t<!std::is_trivially_copyable_v<T>>* = nullptr) {}
};

Live example

在这个版本中,构造函数模板被很好地继承了,并且正如人们所期望的那样用于重载决议。


将您自己的 SFINAE 检查移入 c'tor 的参数,而不更改 Foo 似乎也可以解决它:

struct Foo {
    Foo() = default;

    template<typename T, std::enable_if_t<std::is_trivially_copyable_v<T>>* = nullptr>
    Foo(T& object) {}
};

struct Bar : public Foo {
    using Foo::Foo;

    template<typename T>
    Bar(T& object, std::enable_if_t<!std::is_trivially_copyable_v<T>>* = nullptr) {}
};

Live example

Clang 认为原始模板的签名与基类中的签名相同。因此它认为基类版本是隐藏的。

【讨论】:

  • 我在why-should-i-avoid-stdenable-if-in-function-signatures 的回答中处理非构造函数的情况。对我来说,clang 是正确的(至少对于函数而言),但我会说这是一个措辞缺陷。
  • @Jarod42 - 我同意这是一个措辞缺陷。在我看来,应该考虑模板的完整签名。
猜你喜欢
  • 2018-12-12
  • 1970-01-01
  • 1970-01-01
  • 2014-11-22
  • 1970-01-01
  • 2016-03-24
  • 2011-12-27
  • 2017-11-17
相关资源
最近更新 更多