-1

I wrote a class to help me compare two std::vectors that won't necessarily have the same length. The class itself is working fine, but GoogleTest isn't using the == operator, so using EXPECT_EQ(foo, bar) fails while EXPECT_TRUE(foo == bar) works:

#include <vector>

#include <gtest/gtest.h>

template<class T>
class MyVector: public std::vector<T> {
public:

    MyVector(std::initializer_list<T> il)
        : std::vector<T>(il){}

    bool operator== (const std::vector<T>& other)
    {
        // Need to static_cast so we don't call this function recursively
        if( this->size() < other.size() ) {
            return (
                static_cast<std::vector<T>&>(*this) == std::vector<T>(other.begin(), other.begin()+this->size())
            );
        } else if( this->size() > other.size() ) {
            return (
                std::vector<T>(this->begin(), this->begin()+other.size()) == other
            );
        }
        return (static_cast<std::vector<T>&>(*this) == other);
    }
};

TEST(SOME_TEST, TestMyVector) {
    MyVector<int> foo {1, 2, 3, 4};
    std::vector<int> bar {1, 2, 3, 4, 5};

    EXPECT_TRUE(foo == bar);

    EXPECT_TRUE(foo == (std::vector<int>{1, 2, 3, 4}));
    EXPECT_TRUE(foo == (std::vector<int>{1, 2, 3}));
    EXPECT_TRUE(foo == (std::vector<int>{1, 2, 3, 4, 5}));

    EXPECT_EQ(foo, bar);
}

When I run the above test, I get:

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from SOME_TEST
[ RUN      ] SOME_TEST.TestMyVector
./my_test.cpp:38: Failure
Expected equality of these values:
  foo
    Which is: { 1, 2, 3, 4 }
  bar
    Which is: { 1, 2, 3, 4, 5 }
[  FAILED  ] SOME_TEST.TestMyVector (0 ms)
[----------] 1 test from SOME_TEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] SOME_TEST.TestMyVector

 1 FAILED TEST

How can I tell GTest to use my class, or what function do I need to write so that EXPECT_EQ works?

Thanks!

Leonardo
  • 1,533
  • 17
  • 28
  • Your `operator==` promises not to modify the operand on the right, but not the left. Is that an oversight? If gtest is passing `const` operands, your function does not match. – Drew Dormann Apr 19 '22 at 16:26
  • Unrelated to your question, take a look at [`std::equal`](https://en.cppreference.com/w/cpp/algorithm/equal) for a hint on how to compare two vectors without creating entire new vectors in the comparison function. – Drew Dormann Apr 19 '22 at 16:30
  • Also I would suggest that this would be better as a free function not named `operator==` that just takes `const std::vector&`s as its arguments, because "A is an initial segment of B, or B is an initial segment of A" is pretty far from what I'd expect "A == B" to mean – Nathan Pierson Apr 19 '22 at 16:31
  • @NathanPierson, thanks for the idea of a free function, but using `EXPECT_EQ(foo, bar)` and `EXPECT_TRUE(begin_eq(foo, bar))` (`begin_eq` being the function from [here](https://stackoverflow.com/a/71929808/6271889)) isn't the same. If the containers differ, GTest will print the diff between them, while using a function that returns a `bool` will just fail, so the diff would have to be implemented in the function. – Leonardo Apr 21 '22 at 18:11
  • 1
    @Leonardo You can make Gtest more informative while using a free function. If you do `EXPECT_PRED2(begin_eq, foo, bar)`, Gtest will format and print the values of `foo` and `bar` that caused `begin_eq(foo, bar)` to be `false`. – Nathan Pierson Apr 21 '22 at 18:41

2 Answers2

2

Drew Dormann in comments is right. Your operator == requires left-hand side operand to be non-const, so when GTest passes arguments as const T&, compiler cannot select your operator and falls back to operator==(std::vector, std::vector).

Making your operator const-correct makes the test pass:

bool operator== (const std::vector<T>& other) const
{
    // Need to static_cast so we don't call this function recursively
    if( this->size() < other.size() ) {
        return (
            static_cast<const std::vector<T>&>(*this) == std::vector<T>(other.begin(), other.begin()+this->size())
        );
    } else if( this->size() > other.size() ) {
        return (
            std::vector<T>(this->begin(), this->begin()+other.size()) == other
        );
    }
    return (static_cast<const std::vector<T>&>(*this) == other);
}

However, this is not the only problem you will encounter, starting from the fact that bar == foo is different than foo == bar and that inheriting from standard containers is bad. Consider replacing this with a free function (see it online):

#include <algorithm>

template<class T>
bool begin_eq(const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    // copy-free
    auto [i1, i2] = std::mismatch(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
    bool they_are_equal = i1 == lhs.end() && i2 == rhs.end();
    bool one_reached_end = i1 != lhs.end() || i2 != rhs.end();
    return they_are_equal || one_reached_end;

    //could be also done with C++20 views
} 
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
0

Thanks for the comments, everyone.

@yksisarvinen, thank you so much for suggesting a non-copy comparison. Unfortunately the begin_eq function was failing for EXPECT_FALSE(begin_eq(foo, std::vector<int>{1, 2, 3, 0})); because in this case i2 != rhs.end().

You and Nathan Pierson are correct that a free function is much better, I was over-complicating things by overloading the == operator.

My current code is:

#include <algorithm>

#include <gtest/gtest.h>

template<class T>
bool begin_eq(const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    // copy-free
    auto [i1, i2] = std::mismatch(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
    bool they_are_equal = i1 == lhs.end() && i2 == rhs.end();
    bool one_reached_end = i1 != lhs.end() || i2 != rhs.end();
    return they_are_equal || one_reached_end;

    //could be also done with C++20 views
}

template<typename T>
bool another_begin_eq(const std::vector<T>& lhs, const std::vector<T>& rhs)
{
    // copy-free
    auto lhs_it = lhs.begin();
    auto rhs_it = rhs.begin();
    size_t size = (lhs.size() <= rhs.size() ? lhs.size() : rhs.size());
    for(size_t i = 0; i < size; i++) {
        if( *lhs_it++ != *rhs_it++) return false;
    }
    return true;
} 

TEST(SOME_TEST, TestMyVector) {
    std::vector<int> foo {1, 2, 3, 4};
    std::vector<int> bar {1, 2, 3, 4, 5};

    EXPECT_FALSE(foo == bar);

    EXPECT_TRUE(begin_eq(foo, bar));

    EXPECT_TRUE(begin_eq(foo, std::vector<int>{1, 2, 3, 4}));
    EXPECT_TRUE(begin_eq(foo, std::vector<int>{1, 2, 3}));
    EXPECT_TRUE(begin_eq(foo, std::vector<int>{1, 2, 3, 4, 5}));

    EXPECT_FALSE(another_begin_eq(foo, std::vector<int>{1, 2, 3, 0}));
    EXPECT_FALSE(another_begin_eq(foo, std::vector<int>{1, 2, 0}));
    EXPECT_FALSE(another_begin_eq(foo, std::vector<int>{1, 2, 3, 0, 5}));

    EXPECT_FALSE(begin_eq(foo, std::vector<int>{1, 2, 3, 0}));
    EXPECT_FALSE(begin_eq(foo, std::vector<int>{1, 2, 0}));
    EXPECT_FALSE(begin_eq(foo, std::vector<int>{1, 2, 3, 0, 5}));

    EXPECT_NE(foo, bar);
}

Which yields:

[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from SOME_TEST
[ RUN      ] SOME_TEST.TestMyVector
/untether/workspace/imAIgine/runtimeExports/libuntetherprocessing/tests/test_my_vector.cpp:46: Failure
Value of: begin_eq(foo, std::vector<int>{1, 2, 3, 0})
  Actual: true
Expected: false
/untether/workspace/imAIgine/runtimeExports/libuntetherprocessing/tests/test_my_vector.cpp:47: Failure
Value of: begin_eq(foo, std::vector<int>{1, 2, 0})
  Actual: true
Expected: false
/untether/workspace/imAIgine/runtimeExports/libuntetherprocessing/tests/test_my_vector.cpp:48: Failure
Value of: begin_eq(foo, std::vector<int>{1, 2, 3, 0, 5})
  Actual: true
Expected: false
[  FAILED  ] SOME_TEST.TestMyVector (0 ms)
[----------] 1 test from SOME_TEST (0 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] SOME_TEST.TestMyVector
Leonardo
  • 1,533
  • 17
  • 28