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

Reply via email to