This is an archive of the discontinued LLVM Phabricator instance.

[libc++] Use placeholder return types to avoid hard errors during overload resolution
Needs RevisionPublic

Authored by Arcoth on May 3 2017, 12:46 PM.

Details

Summary

See https://bugs.llvm.org/show_bug.cgi?id=32856. Basically, as this code was written before placeholder return types for functions were a
thing, the function call operators of bind apply decltype on the call expression to infer the return type. This can lead to hard errors when we pass
template functors. Another consequence of this is that the validity or invalidity of these call expressions influences overload resolution (via
SFINAE), hence the const version could be selected where the
bind object was non-const, which is not the prescribed behaviour. This is fixed by
using placeholder return types.

Event Timeline

Arcoth created this revision.May 3 2017, 12:46 PM
EricWF edited edge metadata.May 3 2017, 1:34 PM

Please add a test for this.

EricWF requested changes to this revision.May 3 2017, 3:35 PM

This change regresses other code. By changing __bind_return<...>::type into decltype(auto) you prevent the member functions from SFINAE'ing. For example:

#include <functional>

struct Func {
  template <class ...Args>
  void operator()(Args&&...) = delete;

  template <class ...Args>
  void operator()(Args&&...) const {}
};

int main() {
    Func f;
    std::bind(f)(); // Broken after your change.
}

This patch cannot introduce regressions but changing to using decltype(auto) does. I'm not sure the behavior you want is possible. The library is required to evaluate the validity of calling the specified functor in the signature of the call operator in order to correctly SFINAE.
I think this makes accepting the code in PR32856 impossible.

This revision now requires changes to proceed.May 3 2017, 3:35 PM
Arcoth added a comment.EditedMay 3 2017, 4:31 PM

This change regresses other code. By changing __bind_return<...>::type into decltype(auto) you prevent the member functions from SFINAE'ing. For example:

#include <functional>

struct Func {
  template <class ...Args>
  void operator()(Args&&...) = delete;

  template <class ...Args>
  void operator()(Args&&...) const {}
};

int main() {
    Func f;
    std::bind(f)(); // Broken after your change.
}

This patch cannot introduce regressions but changing to using decltype(auto) does. I'm not sure the behavior you want is possible. The library is required to evaluate the validity of calling the specified functor in the signature of the call operator in order to correctly SFINAE.
I think this makes accepting the code in PR32856 impossible.

This is intended to fail (as I also mentioned in the motivating SO post). The type of the object argument fd is FD, where FD is decay_t<Func>, i.e. Func. That's it. For non-const object arguments, overload resolution prefers non-const member functions per se, hence we should e.g. get a diagnostic saying that a deleted function is called. Alternatively, some other construct inside bind should fail: libstdc++'s bind somehow requires result_of_t<Func&()>, which fails.

In short, this was never well-formed; the fact that it compiled is a consequence of the return type being denoted in an expression SFINAE manner.