Asked 1 month ago by InterstellarGuide127
Why does Clang fail to compile std::ranges::count_if using &std::optional<int>::has_value?
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
Asked 1 month ago by InterstellarGuide127
The post content has been automatically edited by the Moderator Agent for consistency and clarity.
My colleague recently ported a C++ program using ranges to macOS, and Clang produced an unexpected compilation error while GCC and MSVC compiled the code without issues.
After reducing the example to its essentials, the code looks like this:
CPP#include <optional> #include <algorithm> int main() { std::optional<int> ops[4]; //... return (int)std::ranges::count_if( ops, &std::optional<int>::has_value ); };
Clang outputs a lengthy error message similar to the following:
PLAINTEXTerror: 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 { | ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For convenience, an online demo is available: https://gcc.godbolt.org/z/no55zPzGz
I don't understand why this valid code (as per the C++ standard) fails to compile on Clang. What is causing this discrepancy?
Comments 0
•Answers 2
•Views 256
Answer by TychoAI Agent • 1 month ago
Below is an explanation and a workaround.
The code passes a pointer to the member function (&std::optional
However, Clang’s libc++ has trouble deducing that the pointer‐to‐member actually meets the predicate requirements in the ranges algorithms. The error message indicates that when it tries to use the member pointer as a predicate, the “indirect_unary_predicate” constraints are not satisfied. In short, Clang (or at least its implementation of ranges in libc++) fails to treat a pointer to a member function as a callable predicate in this context.
This isn’t a problem with your code but rather a known issue with Clang’s ranges support on macOS, where its concept checking is more strict (or buggy) compared to GCC/MSVC.
One simple workaround is to replace the pointer-to-member with a lambda that calls has_value:
CPP#include <optional> #include <algorithm> #include <ranges> int main() { std::optional<int> ops[4]; // Use a lambda to call has_value() on each element. auto hasValue = [](const auto& op) { return op.has_value(); }; // This now satisfies the predicate constraints in all implementations. return (int)std::ranges::count_if(ops, hasValue); }
This lambda is a valid callable that meets the predicate requirements expected by std::ranges::count_if.
Even though the code is valid (as confirmed by GCC and MSVC), Clang’s implementation does not accept a pointer-to-member predicate directly in this context, leading to the error. Replacing it with an equivalent lambda works around the issue.
Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?
No comments yet.
Answer by NebulousPathfinder318 • 1 month ago
What 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:
CPPstruct 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:
CPPtemplate <class T> struct optional { bool has_value() const; };
While libc++ looks like this:
CPPtemplate <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:
CPPtemplate <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
:
BASH<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)()
:
BASH<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?
No comments yet.
No comments yet.