Meu colega porta um programa C++ com intervalos no macOS e observa um erro de compilação inesperado.
Após a simplificação máxima, o programa de exemplo fica assim:
#include <optional>
#include <algorithm>
int main() {
std::optional<int> ops[4];
//...
return (int)std::ranges::count_if( ops, &std::optional<int>::has_value );
};
O GCC e o MSVC funcionam bem com o programa, mas o Clang exibe um longo erro:
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 {
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Demonstração online: https://gcc.godbolt.org/z/no55zPzGz
Não entendi o que há de errado com o programa?
O que você está fazendo é tecnicamente UB e pode falhar — não há garantia de que usar ponteiros para funções de membro de classes de biblioteca padrão como essa realmente funcione. Para libc++, não funciona.
Concretamente, a questão recebe uma hierarquia como:
O tipo de
&B::has_value
é obviamente umbool (B::*)() const
. Mas o tipo de&D::has_value
... também é a mesma coisa. Pessoalmente, acho que é um defeito de linguagem e deveria lhe dar umbool (D::*)() const
já que foi isso que você pediu — mas isso provavelmente não pode ser mudado agora, e essas são as regras.Agora, para libstdc++ e MSVCSTL,
&std::optional<int>::has_value
dá a você umbool (std::optional<int>::*)() const
porque eles aparentemente implementam essa função membro diretamente. Mas para libc++, eles aparentemente implementam seuoptional
um pouco, mas diferentemente... então o ponto membro que você obtém de volta é, na verdade, umbool (std::__optional_storage_base<int>::*)() const
. Bem, tambémnoexcept
, mas isso não importa.Agora você pode pensar que isso não importa — afinal, você pode invocar funções de membro de classe base sem problemas, certo? Você pode. A menos que seja uma base privada. O que, neste caso, é.
Em forma reduzida, libstdc++ e MSVCSTL se parecem com isto:
Enquanto libc++ se parece com isto:
O resultado é que, embora
o.has_value()
funcione para todas as implementações, tentar usar&optional<int>::has_value
for libc++ não é invocável porque você obtém um ponteiro para uma função de classe base privada.Aliás, uma das razões pelas quais seria ótimo se ponteiros para membros fossem invocáveis é a qualidade das mensagens de erro.
Considerar:
No clang, esse conceito falha de qualquer forma, já que está verificando a mesma coisa de qualquer forma. Mas a qualidade do erro é bem diferente. Com
std::invoke
:Com
(t.*f)()
:A disparidade do gcc é similar (embora você tenha que fazer
fconcepts-diagnostics-depth=2
). Com a invocação direta, você obtém um diagnóstico sobre a classe base estar inacessível. Cominvoke
você obtém... nada.O que você prefere ver?