llvm / llvm/llvm-project

[clang] Incorrect -Wunneeded-member-function warning in C++20 mode with ranges library code

Open
#203,719 1 comment 0 reactions 0 assignees View on GitHub
clang:diagnostics spaceship
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

This program triggers a -Wunneeded-member-function warning if define INCLUDE_EQUALS_OPERATOR is set (see https://godbolt.org/z/17Tneax4K):
```c++
#include
#include
#include

namespace {

#define INCLUDE_EQUALS_OPERATOR

// The struct is intentionally not declared in any header. It is local to this translation unit.
struct Foo
{
std::strong_ordering operator<=>(const Foo& p_rhs) const; // not defaulted
#if defined(INCLUDE_EQUALS_OPERATOR)
bool operator==(const Foo& p_rhs) const; // not defaulted
#endif

int m_value;
};

std::strong_ordering Foo::operator<=>(const Foo& p_rhs) const
{
return m_value <=> p_rhs.m_value;
}

#if defined(INCLUDE_EQUALS_OPERATOR)
bool Foo::operator==(const Foo& p_rhs) const
{
return m_value == p_rhs.m_value;
}
#endif

} // namespace

int main()
{
std::vector v;
std::ranges::sort(v); // requires operator==() through concept satisfaction
return 0;
}
```

The compiler deduces that operator==() is not used and therefore not needed:

```shell
:26:11: warning: member function 'operator==' is not needed and will not be emitted [-Wunneeded-member-function]
26 | bool Foo::operator==(const Foo& p_rhs) const
| ^~~~~~~~
1 warning generated.
```

However, if the define INCLUDE_EQUALS_OPERATOR is commented out (see https://godbolt.org/z/ooYxqa1fM) then the warning transforms into an error:
```
:37:5: error: no matching function for call to object of type 'const __sort_fn'
37 | std::ranges::sort(v); // requires operator==() through concept satisfaction
| ^~~~~~~~~~~~~~~~~
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/ranges_algo.h:2507:7: note: candidate template ignored: constraints not satisfied [with _Range = std::vector &, _Comp = ranges::less, _Proj = identity]
2507 | operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/ranges_algo.h:2505:16: note: because 'sortable > &>, std::ranges::less, std::identity>' evaluated to false
2505 | requires sortable, _Comp, _Proj>
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/iterator_concepts.h:971:10: note: because 'indirect_strict_weak_order > >, std::identity>>' evaluated to false
971 | && indirect_strict_weak_order<_Rel, projected<_Iter, _Proj>>;
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/iterator_concepts.h:755:10: note: because 'strict_weak_order > >, std::identity>::__type>, __indirect_value_t > >, std::identity>::__type>>' evaluated to false
755 | && strict_weak_order<_Fn&, __indirect_value_t<_I1>, __indirect_value_t<_I2>>
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/concepts:406:33: note: because 'relation' evaluated to false
406 | concept strict_weak_order = relation<_Rel, _Tp, _Up>;
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/concepts:397:9: note: because 'predicate' evaluated to false
397 | = predicate<_Rel, _Tp, _Tp> && predicate<_Rel, _Up, _Up>
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/concepts:391:25: note: because 'regular_invocable' evaluated to false
391 | concept predicate = regular_invocable<_Fn, _Args...>
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/concepts:387:33: note: because 'invocable' evaluated to false
387 | concept regular_invocable = invocable<_Fn, _Args...>;
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/concepts:383:25: note: because 'is_invocable_v' evaluated to false
383 | concept invocable = is_invocable_v<_Fn, _Args...>;
| ^
/opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/17.0.0/../../../../include/c++/17.0.0/bits/ranges_algo.h:2483:7: note: candidate function template not viable: requires at least 2 arguments, but 1 was provided
2483 | operator()(_Iter __first, _Sent __last,
| ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2484 | _Comp __comp = {}, _Proj __proj = {}) const
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
```

The explanation seems to be that std::ranges::sort(v) by default uses std::ranges::less which requires (via a C++20 concept) std::totally_ordered_with and this in turn demands std::equality_comparable. So according to the concept restrictions operator==() is needed, but the warning does not seem to be considering this fact. It only focuses on operator==() being unused in the code itself, which is true.

Contributor guide

Open the contributing guide

Research direction

Start by compiling the provided C++20 reproducer with Clang and -Wunneeded-member-function, then trace the warning's analysis of std::ranges::sort, std::ranges::less, and the equality_comparable concepts. Done means operator== is not falsely reported as unneeded when concept satisfaction requires it, while genuinely unused member functions still receive the warning.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.