gcc not_fn 实现:为什么 _Not_fn 接受额外的 int 参数?
gcc not_fn implementation: why does _Not_fn accept additional int parameter?
最近看了一下gcc提供的implementation of std::not_fn
函数模板。
此函数模板的 return 类型是 _Not_fn
- 包装器 class 模板,它否定包装的可调用对象。
事实证明,_Not_fn
constructor 接受一个未明确使用的额外 int
参数:
template<typename _Fn2>
_Not_fn(_Fn2&& __fn, int)
: _M_fn(std::forward<_Fn2>(__fn)) { }
对构造函数的调用如下所示:
template<typename _Fn>
inline auto not_fn(_Fn&& __fn)
noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>, _Fn&&>::value)
{
return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; // <- 0 is passed here
}
问题:
此附加 int
参数的用途是什么?为什么 gcc 实现需要它?
我可以想到两个原因。
第一个原因是,接受两个参数的构造函数不是转换构造函数。有时甚至会意外调用或选择显式转换以进行重载。通过添加 int
,有关可兑换性的问题很明确(不是)。
第二个原因可能是它是来自重载解析排序技巧的遗留代码。如果您创建两个重载,一个需要 int
,另一个需要 ...
,当两者都可行时,int
将优先于 ...
。
如果在某一时刻该类型具有更复杂的构造,它可能有 int
和 ...
重载。 int
可能只是未清理代码的遗留问题。
添加虚拟参数是因为实施者不希望完美的转发构造函数是 better match 而不是非 const
参数的复制构造函数。
考虑这个例子
struct _Not_fn
{
template<typename _Fn2>
_Not_fn(_Fn2&&)
{ /* */ }
_Not_fn(const _Not_fn&)
{ /* */ }
};
_Not_fn f([]{});
_Not_fn f1(f); // calls perfect forwarding constructor
_Not_fn f2(const_cast<const _Not_fn&>(f)); // calls copy constructor
移动构造函数也存在同样的问题。虚拟 int
参数的引入解决了这个令人头疼的问题。
一个题外话的旁注:libstdcx++ 明确定义了这些 ctors:
template<typename _Fn2>
_Not_fn(_Fn2&& __fn, int)
: _M_fn(std::forward<_Fn2>(__fn)) { }
_Not_fn(const _Not_fn& __fn) = default;
_Not_fn(_Not_fn&& __fn) = default;
~_Not_fn() = default;
/*other member functions*/
template<typename _Fn>
inline auto
not_fn(_Fn&& __fn)
noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>, _Fn&&>::value)
{
return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0};
}
But libcxx doesn't declare any ctor explicitly
__not_fn_imp() = delete;
__not_fn_imp
┃ ┃ (Class)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ __fd (Field)
所以,
template <class _RawFunc>
inline _LIBCPP_INLINE_VISIBILITY __not_fn_imp<decay_t<_RawFunc> >
not_fn(_RawFunc&& __fn) {
return __not_fn_imp<decay_t<_RawFunc> >(_VSTD::forward<_RawFunc>(__fn));
}
可以找到正确的 (cpoy)ctor。
最近看了一下gcc提供的implementation of std::not_fn
函数模板。
此函数模板的 return 类型是 _Not_fn
- 包装器 class 模板,它否定包装的可调用对象。
事实证明,_Not_fn
constructor 接受一个未明确使用的额外 int
参数:
template<typename _Fn2>
_Not_fn(_Fn2&& __fn, int)
: _M_fn(std::forward<_Fn2>(__fn)) { }
对构造函数的调用如下所示:
template<typename _Fn>
inline auto not_fn(_Fn&& __fn)
noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>, _Fn&&>::value)
{
return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0}; // <- 0 is passed here
}
问题:
此附加 int
参数的用途是什么?为什么 gcc 实现需要它?
我可以想到两个原因。
第一个原因是,接受两个参数的构造函数不是转换构造函数。有时甚至会意外调用或选择显式转换以进行重载。通过添加 int
,有关可兑换性的问题很明确(不是)。
第二个原因可能是它是来自重载解析排序技巧的遗留代码。如果您创建两个重载,一个需要 int
,另一个需要 ...
,当两者都可行时,int
将优先于 ...
。
如果在某一时刻该类型具有更复杂的构造,它可能有 int
和 ...
重载。 int
可能只是未清理代码的遗留问题。
添加虚拟参数是因为实施者不希望完美的转发构造函数是 better match 而不是非 const
参数的复制构造函数。
考虑这个例子
struct _Not_fn
{
template<typename _Fn2>
_Not_fn(_Fn2&&)
{ /* */ }
_Not_fn(const _Not_fn&)
{ /* */ }
};
_Not_fn f([]{});
_Not_fn f1(f); // calls perfect forwarding constructor
_Not_fn f2(const_cast<const _Not_fn&>(f)); // calls copy constructor
移动构造函数也存在同样的问题。虚拟 int
参数的引入解决了这个令人头疼的问题。
一个题外话的旁注:libstdcx++ 明确定义了这些 ctors:
template<typename _Fn2>
_Not_fn(_Fn2&& __fn, int)
: _M_fn(std::forward<_Fn2>(__fn)) { }
_Not_fn(const _Not_fn& __fn) = default;
_Not_fn(_Not_fn&& __fn) = default;
~_Not_fn() = default;
/*other member functions*/
template<typename _Fn>
inline auto
not_fn(_Fn&& __fn)
noexcept(std::is_nothrow_constructible<std::decay_t<_Fn>, _Fn&&>::value)
{
return _Not_fn<std::decay_t<_Fn>>{std::forward<_Fn>(__fn), 0};
}
But libcxx doesn't declare any ctor explicitly
__not_fn_imp() = delete;
__not_fn_imp
┃ ┃ (Class)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ operator() (Method)
┃ ┃ __fd (Field)
所以,
template <class _RawFunc>
inline _LIBCPP_INLINE_VISIBILITY __not_fn_imp<decay_t<_RawFunc> >
not_fn(_RawFunc&& __fn) {
return __not_fn_imp<decay_t<_RawFunc> >(_VSTD::forward<_RawFunc>(__fn));
}
可以找到正确的 (cpoy)ctor。