SFINAEd-out函数是否会影响基类中显式导入的重载

时间:2015-11-13 13:46:35

标签: c++ c++11 language-lawyer sfinae shadowing

在遇到其他设计的问题之后,我决定make make包装类将重载添加到基类的某些成员函数中,当且仅当基类中不存在可行的重载时。基本上,这就是我想要做的事情:

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(我将std::enable_if简化为最简单的东西;原始的main涉及检测成语)。但是,g ++和clang ++不同意。采取以下int main() { assert(wrapper<bar>{}.foo(0)); }

foo

g ++没问题:来自wrapper<bar>的{​​{1}}是SFINAEd,所以它使用了来自bar的{​​{1}}。另一方面,wrapper<bar>::foo 总是阴影bar::foo的{​​{3}},即使SFINAE出局也是如此。以下是错误消息:

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

3 个答案:

答案 0 :(得分:7)

考虑§10.2:

  

在声明集中, using-declaration 被替换为   指定成员未被隐藏或被成员覆盖   派生类(7.3.3),

§7.3.3

  

using-declaration 将基类中的名称带入派生类范围时,派生类中的成员函数模板会覆盖和/或隐藏成员函数和成员函数具有相同名称的模板, parameter-type-list (8.3.5 [dcl.fct]),cv-qualification和 ref-qualifier (如果有)基类(而不是冲突)。

显然,您的示例中唯一的区别在于返回类型。因此,Clang是正确的,并且GCC被窃听。

CWG #1764引入了措辞:

  

根据7.3.3 [namespace.udecl]第15段,

     
    

当using声明将基类中的名称带入派生类范围时,[...]

  
     

10.2中给出的类范围名称查找算法   但是,[class.member.lookup]没有实现这个要求;   没有什么可以删除隐藏的基类成员(替换   结果集中的 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

答案 1 :(得分:2)

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

你的代码不正确,因为没有理由在两者都有效的情况下更喜欢限制版本到基础版本,所以我相信如果你传递的东西不是默认构造的,你应该会出现歧义错误

答案 2 :(得分:1)

正如@SebastianRedl所说,这看起来像一个铿锵的bug(编辑:看起来像我们wrong)。

无论哪种编译器是正确的,可能的解决方法是定义两个版本的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