Issue |
138404
|
Summary |
[clang++] Bugs on overloading operator== and operator!=
|
Labels |
clang
|
Assignees |
|
Reporter |
ConnectionFailedd
|
I'm writing a generative meta-programming library. Below is the code that triggers the bugs.
``` C++
// test.cpp
#include <concepts>
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
class CppExpression;
template<typename T1, typename T2>
concept same_after_decay = std::same_as<std::decay_t<T1>, std::decay_t<T2>>;
template<typename T>
concept is_cpp_expression = same_after_decay<T, CppExpression>;
template<typename T>
concept convertible_to_cpp_expression = is_cpp_expression<T>
// below are for generating literals
|| same_after_decay<T, int> || same_after_decay<T, unsigned int> || same_after_decay<T, long> || same_after_decay<T, unsigned long> || same_after_decay<T, long long> || same_after_decay<T, unsigned long long>
|| same_after_decay<T, float> || same_after_decay<T, double> || same_after_decay<T, long double>
|| same_after_decay<T, char> || same_after_decay<T, char *> || same_after_decay<T, const char *> || same_after_decay<T, std::string> || same_after_decay<T, std::string_view>
|| same_after_decay<T, bool> || same_after_decay<T, std::nullptr_t>;
class CppExpression {
public:
enum class Precedence {
LOWEST = 0,
COMMA = 1,
ASSIGN = 2,
LOGICAL_OR = 3,
LOGICAL_AND = 4,
BITWISE_OR = 5,
BITWISE_XOR = 6,
BITWISE_AND = 7,
EQUALITY = 8,
COMPARISON = 9,
SHIFT = 10,
SUM = 11,
PRODUCT = 12,
PREFIX = 13,
SUFFIX = 14,
HIGHEST = 15
};
private:
std::string expression_;
Precedence precedence_;
CppExpression(std::string _expression_, Precedence precedence = Precedence::LOWEST) : expression_(std::move(_expression_)), precedence_(precedence) {}
CppExpression(const CppExpression & other) : expression_(other.expression_), precedence_(other.precedence_) {}
CppExpression(CppExpression && other) noexcept : expression_(std::move(other.expression_)), precedence_(other.precedence_) {}
public:
static CppExpression create_expression(const std::string & _expression_, Precedence precedence = Precedence::LOWEST) { return CppExpression(_expression_, precedence); }
static CppExpression create_termial(const std::string & _expression_) { return CppExpression(_expression_, Precedence::HIGHEST); }
template<convertible_to_cpp_expression T>
static CppExpression create_literal(T && value) {
if constexpr(same_after_decay<T, int>) { return CppExpression(std::to_string(value), Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, unsigned int>) { return CppExpression(std::to_string(value) + 'u', Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, long>) { return CppExpression(std::to_string(value) + 'l', Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, unsigned long>) { return CppExpression(std::to_string(value) + "ul", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, long long>) { return CppExpression(std::to_string(value) + "ll", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, unsigned long long>) { return CppExpression(std::to_string(value) + "ull", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, float>) { return CppExpression(std::to_string(value) + 'f', Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, double>) { return CppExpression(std::to_string(value), Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, long double>) { return CppExpression(std::to_string(value) + 'l', Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, char>) { return CppExpression("'" + std::string(1, value) + "'", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, char *>) { return CppExpression("\"" + std::string(value) + "\"", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, const char *>) { return CppExpression("\"" + std::string(value) + "\"", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, std::string>) { return CppExpression("\"" + value + "\"", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, std::string_view>) { return CppExpression("\"" + std::string(value) + "\"", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, bool>) { return CppExpression(value ? "true" : "false", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, std::nullptr_t>) { return CppExpression("nullptr", Precedence::HIGHEST); }
else if constexpr(same_after_decay<T, CppExpression>) { return CppExpression(value._expression_(), value.precedence()); }
else { static_assert(false, "Invalid C++ literal type"); }
}
const std::string & _expression_() const { return expression_; }
Precedence precedence() const { return precedence_; }
};
template<convertible_to_cpp_expression T1, convertible_to_cpp_expression T2>
requires is_cpp_expression<T1> || is_cpp_expression<T2>
CppExpression operator==(T1 && lhs, T2 && rhs) {
return CppExpression::create_expression(CppExpression::create_literal(lhs)._expression_() + " == " + CppExpression::create_literal(rhs)._expression_(), CppExpression::Precedence::EQUALITY);
}
template<convertible_to_cpp_expression T1, convertible_to_cpp_expression T2>
requires is_cpp_expression<T1> || is_cpp_expression<T2>
CppExpression operator!=(T1 && lhs, T2 && rhs) {
return CppExpression::create_expression(CppExpression::create_literal(lhs)._expression_() + " != " + CppExpression::create_literal(rhs)._expression_(), CppExpression::Precedence::EQUALITY);
}
template<convertible_to_cpp_expression T1, convertible_to_cpp_expression T2>
requires is_cpp_expression<T1> || is_cpp_expression<T2>
CppExpression operator>(T1 && lhs, T2 && rhs) {
return CppExpression::create_expression(CppExpression::create_literal(lhs)._expression_() + " > " + CppExpression::create_literal(rhs)._expression_(), CppExpression::Precedence::EQUALITY);
}
int main() {
auto var_a = CppExpression::create_termial("a");
std::cout << (var_a == 1)._expression_() << std::endl;
std::cout << (var_a != 1)._expression_() << std::endl;
std::cout << (var_a > 1)._expression_() << std::endl;
}
```
**Compiling this code with clang++ takes extremely long time:**
``` shell
$ # clang++ 20.1.2 | Apple M3 | MacOS 15.4.1
$ time clang++ -std=c++23 test.cpp
clang++ -std=c++23 test.cpp 132.19s user 0.29s system 99% cpu 2:12.60 total
```
On another machine:
``` shell
$ # clang++ 18.1.3 | Intel Core i9 14900K | Ubuntu 24.04
$ time clang++ -std=c++23 test.cpp
clang++ -std=c++23 test.cpp 184.87s user 0.07s system 99% cpu 3:04.95 total
```
While g++ performs like:
``` shell
$ # g++ 13.3.0 | Intel Core i9 14900K | Ubuntu 24.04
$ time g++ -std=c++23 test.cpp
g++ -std=c++23 test.cpp 0.15s user 0.04s system 95% cpu 0.202 total
```
And according to my observation:
- Call to `operator!=` is the reason of long compile time here. Removing line `std::cout << (var_a != 1)._expression_() << std::endl;`, clang++ can compile fastly.
- But if removing the definition of `operator!=`, then call to `operator==` will cause a similarly long compile time.
- Except these two operator, any other binary operator has the same effect.
- The underlying reason may be the concept `convertible_to_cpp_expression`. If we reduce the options of literals, e.g. remove all options after `long double`, the compile time will be short.
Anyway, I think it is a bug because g++ can easily compile such file. However, due to my limited capabilities, I can only hope that you will be able to address it.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs