在遇到其他设计的问题之后,我决定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
?
答案 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);
}
答案 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
}
};