最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c++ - Counting the number of present values in array of optionals with std::ranges - Stack Overflow

programmeradmin2浏览0评论

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
  • As a data point, [](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:25
  • Looks like libc++ can't handle &std::optional<int>::has_value as a predicate. – NathanOliver Commented Feb 7 at 21:28
Add a comment  | 

1 Answer 1

Reset to default 15

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:

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?

发布评论

评论列表(0)

  1. 暂无评论