Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 775
Edgar Rokjān Avatar asked Jan 02 '23 10:01

Edgar Rokjān


2 Answers

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.

like image 129
Praetorian Avatar answered Jan 04 '23 23:01

Praetorian


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.

like image 27
Yakk - Adam Nevraumont Avatar answered Jan 04 '23 22:01

Yakk - Adam Nevraumont