gcc not_fn implementation: why does _Not_fn accept additional int parameter?
Asked Answered
D

3

5

Recently I have taken a look at the implementation of std::not_fn function template provided by gcc.

The return type of this function template is _Not_fn - a wrapper class template which negates a wrapped callable object.

It turns out, that _Not_fn constructor accepts an additional int parameter which is not explicitly used:

template<typename _Fn2>
    _Not_fn(_Fn2&& __fn, int)
    : _M_fn(std::forward<_Fn2>(__fn)) { }

The call to the constructor looks like that:

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
}

Question:

What is the purpose of this additional int parameter? Why does gcc implementation need it?

Doreathadoreen answered 8/5, 2018 at 19:11 Comment(1)
This is common enough for prioritizing overloads when SFINAE is involved. Perhaps it used to be implemented differently and this parameter was left in.Heeled
R
9

The dummy parameter was added because the implementer didn't want the perfect forwarding constructor to be a better match than the copy-constructor for non-const arguments.

Consider this example

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

The same problem exists for the move constructor as well. The introduction of the dummy int parameter solves this headache.

Live example

The change was introduced to fix bug 70564.

Recruitment answered 8/5, 2018 at 19:21 Comment(4)
Here's the commit: github.com/gcc-mirror/gcc/commit/…Heeled
@Heeled Thanks for finding that! I've linked to the PR and the commit in the answer.Recruitment
Now it makes sense for me! I feel that if I were an implementer I would do the same bug...Response
, int sure beats writing that constraint!Chub
S
1

I can think of two reasons.

The first reason is, a constructor that takes 2 arguments is not a converting constructor. Even explicit conversions can sometimes be called or selected for overload by accident. By adding int, questions about convertibility are clear (it isn't).

A second reason could be that it is legacy code from an overload resolution ordering trick. If you create two overloads, one which takes int the other ..., the int will be picked over the ... when both are viable.

If at one point the type had a more complex construction it could have had that int and ... overload. The int may just be a legacy of uncleaned-up code.

Submarine answered 8/5, 2018 at 19:20 Comment(0)
S
0

An off-topic sidenote: libstdcx++ defines these ctors explicitly:

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)

So,

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));
}

Can find the correct (cpoy)ctor.

Supernormal answered 6/11, 2018 at 8:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.