Bug 65942 - [5/6 Regression] [C++14] cannot use std::function as comparator in algorithms
Summary: [5/6 Regression] [C++14] cannot use std::function as comparator in algorithms
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 5.0
: P3 normal
Target Milestone: 5.2
Assignee: Jason Merrill
URL:
Keywords: rejects-valid
Depends on:
Blocks: 58281
  Show dependency treegraph
 
Reported: 2015-04-30 09:03 UTC by Avi Kivity
Modified: 2018-06-01 20:34 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed: 2015-04-30 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Avi Kivity 2015-04-30 09:03:48 UTC
Too much template magic causes gcc 5.1 to reject the following valid code:



#include <experimental/optional>
#include <algorithm>
#include <functional>

using T1 = int;
using T2 = std::vector<T1>;

bool cmp1(const T1& a, const T1& b) { return a < b; }
std::function<bool (const T1&, const T1&)> cmp2 = cmp1;

int main(int ac, char** av) {
  T2 v;
  std::sort(v.begin(), v.end(), cmp1); // works
  std::sort(v.begin(), v.end(), cmp2); // fails
}

Even though the two calls to sort() should be identical, the second one does not compile.

This is a regression from 4.9
Comment 1 Jonathan Wakely 2015-04-30 09:14:14 UTC
The <experimental/optional> header is not relevant to the bug.

The error only happens in C++14 mode.

#include <algorithm>
#include <functional>

bool cmp1(const int& a, const int& b) { return a < b; }
std::function<bool (const int&, const int&)> cmp2 = cmp1;

int main() {
  int v[1];
  std::sort(v, v, cmp1); // works
  std::sort(v, v, cmp2); // fails
}
Comment 2 Jonathan Wakely 2015-04-30 09:22:52 UTC
It's due to the addition of C++14 constexpr throughout the library, the program works if preceded by 

#include <bits/c++config.h>
#undef _GLIBCXX14_CONSTEXPR
#define _GLIBCXX14_CONSTEXPR

so that the newly-constexpr functions are not constexpr.
Comment 3 Jonathan Wakely 2015-04-30 09:32:19 UTC
This seems to be a compiler bug not a library bug:

#include <functional>

bool cmp1(const int& a, const int& b) { return a < b; }
std::function<bool (const int&, const int&)> cmp2 = cmp1;

  template<typename _Compare>
    struct _Iter_comp_iter
    {
      _Compare _M_comp;
      _Iter_comp_iter(_Compare __comp)
	: _M_comp(__comp)
      { }

      template<typename _Iterator1, typename _Iterator2>
#if __cplusplus >= 201402L
        constexpr
#endif
        bool
        operator()(_Iterator1 __it1, _Iterator2 __it2)
        { return bool(_M_comp(*__it1, *__it2)); }
    };

  template<typename _Compare>
    inline _Iter_comp_iter<_Compare>
    __iter_comp_iter(_Compare __comp)
    { return _Iter_comp_iter<_Compare>(__comp); }

int main() {
  int v[1];
  int* iter = v;
  auto cmp = __iter_comp_iter(cmp2);
  cmp(iter, iter);
}
Comment 4 Markus Trippelsdorf 2015-04-30 09:46:07 UTC
Well, clang rejects your testcase, too.

test2.cpp:20:31: error: indirection requires pointer operand ('int' invalid)
        { return bool(_M_comp(*__it1, *__it2)); }
                              ^~~~~~
/usr/lib64/gcc/x86_64-pc-linux-gnu/5.1.1/include/g++-v5/functional:1981:27: note: in instantiation of function template specialization '_Iter_comp_iter<std::function<bool
      (const int &, const int &)> >::operator()<int, int>' requested here
        using _Invoke = decltype(__callable_functor(std::declval<_Functor&>())
                                 ^
/usr/lib64/gcc/x86_64-pc-linux-gnu/5.1.1/include/g++-v5/functional:1990:2: note: in instantiation of template type alias '_Invoke' requested here
        using _Callable
        ^
/usr/lib64/gcc/x86_64-pc-linux-gnu/5.1.1/include/g++-v5/functional:2057:30: note: in instantiation of template type alias '_Callable' requested here
               typename = _Requires<_Callable<_Functor>, void>>
                                    ^
/usr/lib64/gcc/x86_64-pc-linux-gnu/5.1.1/include/g++-v5/functional:2058:2: note: in instantiation of default argument for 'function<_Iter_comp_iter<std::function<bool
      (const int &, const int &)> > >' required here
        function(_Functor);
        ^~~~~~~~
test2.cpp:31:14: note: while substituting deduced template arguments into function template 'function' [with _Functor = _Iter_comp_iter<std::function<bool
      (const int &, const int &)> >, $1 = (no value)]
  auto cmp = __iter_comp_iter(cmp2);
             ^
1 error generated.
Comment 5 Avi Kivity 2015-04-30 10:03:34 UTC
That's clang + libstdc++-5.1, so if the problem is in libstdc++, it's the same bug.
Comment 6 Markus Trippelsdorf 2015-04-30 10:05:26 UTC
(In reply to Avi Kivity from comment #5)
> That's clang + libstdc++-5.1, so if the problem is in libstdc++, it's the
> same bug.

Yes. clang++ -stdlib=libc++ -std=gnu++14 original_testcase.cpp 
is also fine.
Comment 7 Jonathan Wakely 2015-04-30 10:06:11 UTC
Yes, and so do 4.8 and 4.9 for this reduced form with no library dependencies. This fails with -std=c++11 but compiles OK with -std=c++11 -DOK.

EDG accepts it without -DOK


template<typename _Tp>
  _Tp&& declval() noexcept;

template<typename> struct function;

template<typename _Res, typename... _ArgTypes>
  struct function<_Res(_ArgTypes...)>
  {
    template<typename _Functor,
             typename = decltype( declval<_Functor&>()(declval<_ArgTypes>()...) )>
      function(_Functor) { }

    function() { }

    _Res operator()(_ArgTypes...) const;
  };


  template<typename _Compare>
    struct _Iter_comp_iter
    {
      _Compare _M_comp;

      _Iter_comp_iter(_Compare __comp)
	: _M_comp(__comp)
      { }

      template<typename _Iterator1, typename _Iterator2>
#ifndef OK
        constexpr
#endif
        bool
        operator()(_Iterator1 __it1, _Iterator2 __it2)
        { return bool(_M_comp(*__it1, *__it2)); }
    };

using F = function<bool (int, int)>;
F f;
_Iter_comp_iter<F> c{ f };
auto c2 = c;



f.cc: In instantiation of ‘constexpr bool _Iter_comp_iter<_Compare>::operator()(_Iterator1, _Iterator2) const [with _Iterator1 = int; _Iterator2 = int; _Compare = function<bool(int, int)>]’:
f.cc:10:54:   required by substitution of ‘template<class _Functor, class> function<_Res(_ArgTypes ...)>::function(_Functor) [with _Functor = _Iter_comp_iter<function<bool(int, int)> >; <template-parameter-1-2> = <missing>]’
f.cc:40:11:   required from here
f.cc:34:31: error: invalid type argument of unary ‘*’ (have ‘int’)
         { return bool(_M_comp(*__it1, *__it2)); }
                               ^
f.cc:34:39: error: invalid type argument of unary ‘*’ (have ‘int’)
         { return bool(_M_comp(*__it1, *__it2)); }
                                       ^


So the regression is in the library adding 'constexpr' to _Iter_comp_iter, not a regression in the compiler, although the problem may be inherent to the SFINAE constraints in std::function.


It seems that defining the implicit copy constructor of _Iter_comp_iter performs overload resolution on the function(_Functor) constructor, which checks the SFINAE constraint, which instantiates _Iter_comp_iter::operator()<int, int> and that fails if it's constexpr, but compiles OK otherwise.
Comment 8 Jonathan Wakely 2015-04-30 10:07:06 UTC
(In reply to Jonathan Wakely from comment #7)
> Yes, and so do 4.8 and 4.9 

That was in response to "clang rejects your testcase, too."
Comment 9 Jonathan Wakely 2015-04-30 10:22:11 UTC
Further reduced:

template<typename _Tp>
  _Tp&& declval() noexcept;

template<typename Arg>
  struct function
  {
    template<typename Functor,
             typename = decltype( declval<Functor&>()(declval<Arg>()) )>
      function(Functor) { }

    function() { }

    bool operator()(Arg) const;
  };


template<typename Compare>
  struct Iter_comp
  {
    Compare m_comp;

    Iter_comp(Compare comp)
      : m_comp(comp)
    { }

    template<typename Iterator>
#ifndef OK
      constexpr
#endif
      bool
      operator()(Iterator it) const
      { return m_comp(*it); }
  };

using F = function<int>;
F f;
Iter_comp<F> c{ f };
auto c2 = c;


GCC says:

f.cc: In instantiation of ‘constexpr bool Iter_comp<Compare>::operator()(Iterator) const [with Iterator = int; Compare = function<int>]’:
f.cc:8:52:   required by substitution of ‘template<class Functor, class> function<Arg>::function(Functor) [with Functor = Iter_comp<function<int> >; <template-parameter-1-2> = <missing>]’
f.cc:38:11:   required from here
f.cc:32:23: error: invalid type argument of unary ‘*’ (have ‘int’)
       { return m_comp(*it); }
                       ^

and Clang says:

f.cc:32:23: error: indirection requires pointer operand ('int' invalid)
      { return m_comp(*it); }
                      ^~~
f.cc:8:35: note: in instantiation of function template specialization 'Iter_comp<function<int> >::operator()<int>' requested here
             typename = decltype( declval<Functor&>()(declval<Arg>()) )>
                                  ^
f.cc:9:7: note: in instantiation of default argument for 'function<Iter_comp<function<int> > >' required here
      function(Functor) { }
      ^~~~~~~~
f.cc:38:11: note: while substituting deduced template arguments into function template 'function' [with Functor = Iter_comp<function<int> >, $1 = (no value)]
auto c2 = c;
          ^
1 error generated.



What I don't understand is why function<int>::function<Comp_iter<function<int>> ever gets considered for overload resolution. Where does that come from and why?


Anyway, it seems we can fix the library by constraining _Iter_comp_iter::operator()


    template<typename Iterator, typename = decltype(declval<Compare&>()(*declval<Iterator>()))>
#ifndef OK
      constexpr
#endif
      bool
      operator()(Iterator it) const
      { return m_comp(*it); }

That prevents it being instantiated as a possible argument to std::function.

Changing component back to libstdc++
Comment 10 Avi Kivity 2015-05-17 16:16:09 UTC
Any chance that the fix can be committed?  gcc 5 is useless for me without this.
Comment 11 Harald van Dijk 2015-05-17 18:20:43 UTC
GCC and clang are instantiating constexpr function bodies when only the signature should be checked (when they appear in unevaluated expressions). A shorter example program:

template <typename T> constexpr int f(T t) { return t; }
int main() { sizeof(f("")); }

This is rejected by GCC and clang, but accepted by Intel and Sun C++. As far as I can tell, it should be accepted. f is used in a context where a definition of f is not required, so f should not be instantiated. f is used in a way that involves overload resolution, so a declaration of f is instantiated, but that declaration does not contain the error that GCC and clang report.

GCC and clang accept it when constexpr is removed.

It's made worse by the fact that it happens even in SFINAE contexts, where such an error should at worst lead to a substitution failure:

template <typename T> constexpr int f(T t) { return t; }
template <typename T, typename = decltype(f(T()))> void g(T) { }
void g(...) { }
int main() { g(""); }

Here, either the use of f(T()) is an error, so overload resolution should discard the first g overload, and the program should be accepted, or the use of f(T()) is valid, so overload resolution should prefer the first g overload, and the program should still be accepted. But even this is rejected by GCC and clang.

Even if this gets worked around in libstdc++, it's probably worth tracking this as a C++ frontend bug as well.

The programs in my comment here are accepted by G++ 4.5.4 (with -std=c++0x), and rejected by 4.6.4 and later.
Comment 12 Harald van Dijk 2015-05-17 20:32:25 UTC
(In reply to Harald van Dijk from comment #11)
> The programs in my comment here are accepted by G++ 4.5.4 (with -std=c++0x),
> and rejected by 4.6.4 and later.

But as G++ 4.5 recognises but simply ignores the constexpr keyword, it's not really a regression. 4.6 is the first that actually implements constexpr functions, and it was broken in that version already.
Comment 13 Jason Merrill 2015-06-02 02:28:57 UTC
Author: jason
Date: Tue Jun  2 02:28:25 2015
New Revision: 224008

URL: https://gcc.gnu.org/viewcvs?rev=224008&root=gcc&view=rev
Log:
	PR c++/65942
	* decl2.c (mark_used): Don't always instantiate constexpr fns.
	* constexpr.c (cxx_eval_call_expression): Instantiate them here.

Added:
    trunk/gcc/testsuite/g++.dg/cpp0x/constexpr-decltype2.C
Modified:
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/cp/decl2.c
Comment 14 Avi Kivity 2015-06-03 18:10:44 UTC
Please consider backporting this to 5.2.
Comment 15 Jason Merrill 2015-06-05 16:25:57 UTC
Author: jason
Date: Fri Jun  5 16:25:26 2015
New Revision: 224157

URL: https://gcc.gnu.org/viewcvs?rev=224157&root=gcc&view=rev
Log:
	PR c++/65942
	* decl2.c (mark_used): Don't always instantiate constexpr fns.
	* constexpr.c (cxx_eval_call_expression): Instantiate them here.

Added:
    branches/gcc-5-branch/gcc/testsuite/g++.dg/cpp0x/constexpr-decltype2.C
Modified:
    branches/gcc-5-branch/gcc/cp/ChangeLog
    branches/gcc-5-branch/gcc/cp/constexpr.c
    branches/gcc-5-branch/gcc/cp/decl2.c
Comment 16 Jason Merrill 2015-06-05 16:26:36 UTC
Fixed for 5.2.