My colleague ports a C++ program with ranges on macOS, and observes an unexpected compilation error.
After maximum simplification, the example program looks like this:
#include <optional>
#include <algorithm>
int main() {
std::optional<int> ops[4];
//...
return (int)std::ranges::count_if( ops, &std::optional<int>::has_value );
};
GCC and MSVC are fine with the program, but Clang displays a long error:
error: no matching function for call to object of type 'const __count_if::__fn'
7 | return (int)std::ranges::count_if( ops, &std::optional<int>::has_value );
| ^~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:62:3: note: candidate template ignored: constraints not satisfied [with _Range = std::optional<int> (&)[4], _Proj = identity, _Predicate = bool (std::__optional_storage_base<int>::*)() const noexcept]
62 | operator()(_Range&& __r, _Predicate __pred, _Proj __proj = {}) const {
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:60:13: note: because 'indirect_unary_predicate<_Bool (std::__optional_storage_base<int>::*)() const noexcept, projected<iterator_t<optional<int> (&)[4]>, identity> >' evaluated to false
60 | indirect_unary_predicate<projected<iterator_t<_Range>, _Proj>> _Predicate>
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__iterator/concepts.h:191:60: note: because 'predicate<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, iter_value_t<__type> &>' evaluated to false
191 | indirectly_readable<_It> && copy_constructible<_Fp> && predicate<_Fp&, iter_value_t<_It>&> &&
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/predicate.h:28:21: note: because 'regular_invocable<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, std::optional<int> &>' evaluated to false
28 | concept predicate = regular_invocable<_Fn, _Args...> && __boolean_testable<invoke_result_t<_Fn, _Args...>>;
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/invocable.h:34:29: note: because 'invocable<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, std::optional<int> &>' evaluated to false
34 | concept regular_invocable = invocable<_Fn, _Args...>;
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/invocable.h:28:3: note: because 'std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...)' would be invalid: no matching function for call to 'invoke'
28 | std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); // not required to be equality preserving
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:54:3: note: candidate function template not viable: requires at least 3 arguments, but 2 were provided
54 | operator()(_Iter __first, _Sent __last, _Predicate __pred, _Proj __proj = {}) const {
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Online demo:
I don't understand what's wrong with the program?
My colleague ports a C++ program with ranges on macOS, and observes an unexpected compilation error.
After maximum simplification, the example program looks like this:
#include <optional>
#include <algorithm>
int main() {
std::optional<int> ops[4];
//...
return (int)std::ranges::count_if( ops, &std::optional<int>::has_value );
};
GCC and MSVC are fine with the program, but Clang displays a long error:
error: no matching function for call to object of type 'const __count_if::__fn'
7 | return (int)std::ranges::count_if( ops, &std::optional<int>::has_value );
| ^~~~~~~~~~~~~~~~~~~~~
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:62:3: note: candidate template ignored: constraints not satisfied [with _Range = std::optional<int> (&)[4], _Proj = identity, _Predicate = bool (std::__optional_storage_base<int>::*)() const noexcept]
62 | operator()(_Range&& __r, _Predicate __pred, _Proj __proj = {}) const {
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:60:13: note: because 'indirect_unary_predicate<_Bool (std::__optional_storage_base<int>::*)() const noexcept, projected<iterator_t<optional<int> (&)[4]>, identity> >' evaluated to false
60 | indirect_unary_predicate<projected<iterator_t<_Range>, _Proj>> _Predicate>
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__iterator/concepts.h:191:60: note: because 'predicate<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, iter_value_t<__type> &>' evaluated to false
191 | indirectly_readable<_It> && copy_constructible<_Fp> && predicate<_Fp&, iter_value_t<_It>&> &&
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/predicate.h:28:21: note: because 'regular_invocable<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, std::optional<int> &>' evaluated to false
28 | concept predicate = regular_invocable<_Fn, _Args...> && __boolean_testable<invoke_result_t<_Fn, _Args...>>;
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/invocable.h:34:29: note: because 'invocable<_Bool (std::__optional_storage_base<int>::*&)() const noexcept, std::optional<int> &>' evaluated to false
34 | concept regular_invocable = invocable<_Fn, _Args...>;
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__concepts/invocable.h:28:3: note: because 'std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...)' would be invalid: no matching function for call to 'invoke'
28 | std::invoke(std::forward<_Fn>(__fn), std::forward<_Args>(__args)...); // not required to be equality preserving
| ^
/opt/compiler-explorer/clang-19.1.0/bin/../include/c++/v1/__algorithm/ranges_count_if.h:54:3: note: candidate function template not viable: requires at least 3 arguments, but 2 were provided
54 | operator()(_Iter __first, _Sent __last, _Predicate __pred, _Proj __proj = {}) const {
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Online demo: https://gcc.godbolt.org/z/no55zPzGz
I don't understand what's wrong with the program?
Share Improve this question asked Feb 7 at 21:17 FedorFedor 21.3k36 gold badges54 silver badges165 bronze badges 2 |1 Answer
Reset to default 15What you are doing is technically UB and is allowed to fail — there is no guarantee that using pointers to member functions of standard library classes like this actually works. For libc++, it doesn't.
Concretely, the issue is given a hierarchy like:
struct B { bool has_value() const; }
struct D : B { };
The type of &B::has_value
is obviously a bool (B::*)() const
. But the type of &D::has_value
... is also that same thing. Personally I think that's a language defect and it should give you a bool (D::*)() const
since that's what you asked for — but that's very likely not changeable right now, and them's the rules.
Now, for libstdc++ and MSVCSTL, &std::optional<int>::has_value
gives you a bool (std::optional<int>::*)() const
because they apparently implement that member function directly. But for libc++, they apparently implement their optional
a little but differently... so the member point your get back is actually a bool (std::__optional_storage_base<int>::*)() const
. Well, also noexcept
, but that doesn't matter.
Now you might think this doesn't matter — after all, you can invoke base class member functions fine, right? You can. Unless it's a private base. Which, in this case, it is.
In reduced form, libstdc++ and MSVCSTL look like this:
template <class T> struct optional { bool has_value() const; };
While libc++ looks like this:
template <class T> struct optional_base { bool has_value() const; };
template <class T> struct optional : private optional_base<T> {
using optional_base<T>::has_value;
};
The result is that while o.has_value()
works for all the implementations, attempting to use &optional<int>::has_value
for libc++ isn't invocable because you get a pointer to a private base class function.
Incidentally, one of the reasons that it would be great if pointers-to-members were invocable is the quality of error messages.
Consider:
template <class F, class T>
concept invocable = requires (F f, T t) {
#if DIRECT
(t.*f)();
#else
std::invoke(f, t);
#endif
};
static_assert(invocable<decltype(&std::optional<int>::has_value), std::optional<int>>);
On clang, this concept fails either way, since it's checking the same thing either way. But the quality of error is quite a bit different. With std::invoke
:
<source>:7:5: note: because 'std::invoke(f, t)' would be invalid: no matching function for call to 'invoke'
7 | std::invoke(f, t);
| ^
With (t.*f)()
:
<source>:7:7: note: because '(t .* f)()' would be invalid: cannot cast 'std::optional<int>' to its private base class 'std::__optional_storage_base<int>'
7 | (t.*f)();
| ^
gcc's disparity is similar (although you have to do fconcepts-diagnostics-depth=2
). With the direct invocation, you get a diagnostic about the base class being inaccessible. With invoke
you get... nothing.
Which would you rather see?
[](std::optional<int> const& x) { return x.has_value(); }
got it to work for me. If you are in a rush to get past the problem. – Eljay Commented Feb 7 at 21:25libc++
can't handle&std::optional<int>::has_value
as a predicate. – NathanOliver Commented Feb 7 at 21:28