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
}
};
考虑§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.
在使用另一个设计遇到问题后,我决定制作一个包装器 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
}
};
考虑§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.