SFINAEd-out 函数是否隐藏了从基 class 显式导入的重载

Does a SFINAEd-out function shadows an explicitly imported overload from the base class

在使用另一个设计遇到问题后,我决定制作一个包装器 class 以向基 class 的某些成员函数添加重载当且仅当可行的重载尚不存在时在基地 class。基本上,这就是我想要做的:

template<typename T>
struct wrapper: T
{
    using T::foo;

    template<typename Arg>
    auto foo(Arg) const
        -> std::enable_if_t<not std::is_constructible<Arg>::value, bool>
    {
        return false;
    }
};

struct bar
{
    template<typename Arg>
    auto foo(Arg) const
        -> bool
    {
        return true;
    }
};

在这个简单的例子中,wrapper 添加一个重载的 foo 只有当来自基础 class 的那个不可行时(我将 std::enable_if 简化为最简单的可能的事情;原来的涉及检测成语)。但是,g++ 和 clang++ 不同意。采取以下 main:

int main()
{
    assert(wrapper<bar>{}.foo(0));
}

g++ 没问题:来自 wrapper<bar>foo 是 SFINAEd,所以它使用来自 bar 的那个。另一方面,clang++ seems to assume wrapper<bar>::foo 总是 阴影 bar::foo,即使 SFINAEd 出来了。这是错误消息:

main.cpp:30:26: error: no matching member function for call to 'foo'
    assert(wrapper<bar>{}.foo(0));
       ~~~~~~~~~~~~~~~^~~

/usr/include/assert.h:92:5: note: expanded from macro 'assert'
  ((expr)                                                               \
        ^

/usr/local/bin/../lib/gcc/x86_64-unknown-linux-gnu/5.2.0/../../../../include/c++/5.2.0/type_traits:2388:44: note: candidate template ignored: disabled by 'enable_if' [with Arg = int]
    using enable_if_t = typename enable_if<_Cond, _Tp>::type;
                                               ^

1 error generated.

那么,谁是对的?这段代码应该像 clang++ 一样被拒绝,还是应该工作并调用 bar::foo?

这似乎是一个错误;使用显式 using 声明,Clang 必须考虑基重载。没有理由不这样做。

不过,您的代码不正确,因为没有理由在两者都有效时更喜欢受限版本而不是基本版本,所以我相信如果您传递的不是默认值,您应该会收到歧义错误-可构造。

正如@SebastianRedl 所说,这看起来像是一个 clang 错误(编辑:看起来我们是 )。

不管哪个编译器是正确的,一种可能的解决方法是定义两个版本的wrapper<T>::foo:一个用于当T::foo不存在时,它提供了一个自定义实现;一个是当它发生时,它将调用转发到基本版本:

template<typename T>
struct wrapper: T
{
    template<typename Arg, typename U=T>
    auto foo(Arg) const
        -> std::enable_if_t<!has_foo<U>::value, bool>
    {
        return false;
    }

    template<typename Arg, typename U=T>
    auto foo(Arg a) const
        -> std::enable_if_t<has_foo<U>::value, bool>
    {
        return T::foo(a); //probably want to perfect forward a
    }
};

Live Demo

考虑§10.2:

In the declaration set, using-declarations are replaced by the set of designated members that are not hidden or overridden by members of the derived class (7.3.3),

然后第 7.3.3 节

When a using-declaration brings names from a base class into a derived class scope, […] member function templates in the derived class override and/or hide member functions and member function templates with the same name, parameter-type-list (8.3.5 [dcl.fct]), cv-qualification, and ref-qualifier (if any) in a base class (rather than conflicting).

显然,您示例中的唯一区别在于 return 类型。因此 Clang 是正确的,而 GCC 是有问题的。

措辞由CWG #1764引入:

According to 7.3.3 [namespace.udecl] paragraph 15,

When a using-declaration brings names from a base class into a derived class scope, […]

10.2中给出的class范围名称查找算法 [class.member.lookup],然而,并没有实现这个要求; 没有什么可以删除隐藏的基础 class 成员(替换 using-declaration,根据结果集中的第 3) 段。

该决议已于 2014 年 2 月移至 DR,因此可能 GCC 尚未实施。


正如@TartanLlama 的回答中提到的,您可以引入一个对口来处理另一种情况。类似于

template <typename Arg, typename=std::enable_if_t<std::is_constructible<Arg>{}>>
decltype(auto) foo(Arg a) const
{
    return T::foo(a);
}

Demo.