4

I am comparing the class. For the below code

#include <string>
#include <set>
#include <tuple>
#include <cassert>


enum class e : bool
{
    positive = true,
    negetive = false
};


class A
{
public:
    int a;
    e e1 : 1;
    
  friend bool operator==(const A&, const A&);
};

 
 bool operator==(const A& lhs, const A& rhs) {
  auto tie = [](const A& a1) {
    return std::tie(a1.a, a1.e1);
  };
  auto x1 = tie(lhs);
  auto x2 = tie(rhs);
  return x1 == x2;   
}

int main()
{
A a1;
a1.a = 10;
a1.e1 = e::positive;

A b1;
b1.a = 10;
b1.e1 = e::positive;

assert(a1 == b1);

}

Output is :

a.out: main.cpp:44: int main(): Assertion `a1 == b1' failed.

Which is wrong, as two of the classes are the same.

However, if I change the line of code from e e1 : 1; to e e1; it gives the right result.

First of I am wondering what does : does in this case? Why the result is wrong after adding this?

Code can be seen here.

Thanks in advance.

Swapnil
  • 1,424
  • 2
  • 19
  • 30
  • 1
    Have you tried to debug your program, to see what your `tie` lambda returns? – Some programmer dude Sep 18 '20 at 13:56
  • 1
    Very interesting. The assert passes with `-O0` on gcc trunk, and `-O0` and `-O1` with clang trunk. Smells like UB, but I don't see it. – cigien Sep 18 '20 at 13:56
  • I cannot reproduce it with msvc16. – vahancho Sep 18 '20 at 13:58
  • 1
    Also, is there a reason you want to use unions and bit-fields for what is essentially a `bool`? By the way, bit-fields have always been a little special, with lots of corner-cases. Perhaps the problem lies with your use of a bit-field instead of using the enumeration as a enumeration? Why not simply plain `e e1;`? Even when using bit-fields the compiler will add padding so it won't save any space if that's your goal. – Some programmer dude Sep 18 '20 at 13:58
  • 1
    `auto tie = [](const A& a1) -> std::tuple { ... }` or just `return std::tie(lhs.a, lhs.e1) == std::tie(rhs.a, rhs.e1);` – Ted Lyngmo Sep 18 '20 at 14:01
  • Talking about enumerations and bit-fields, even though you base the enumeration on `bool` the enumeration symbols themselves will still be *integers*. So there might be signed/unsigned issues when the bit-field is promoted to `int`. – Some programmer dude Sep 18 '20 at 14:01
  • Why you need 'e e1 : 1' when already told to compiler about boolean datatype? BTW this code works fine in VS2019 as it is. – Build Succeeded Sep 19 '20 at 12:55

1 Answers1

7

This function:

auto tie = [](const A& a1) {
    return std::tie(a1.a, a1.e1);
};

returns a std::tuple<int const&, e const &>. Even though the fields that the tuple store are references, there is a special rule for bit fields where a temporary copy of the bit field is made, and a reference is bound to that temporary. This means when you return from the function, the reference to the bit field is dangling, leading to undefined behavior when you use that field of the tuple later.

You can fix this by explicitly specifying the return type:

auto tie = [](const A& a1) -> std::tuple<int, e> {
    return std::tie(a1.a, a1.e1);
};

Here's a demo.

Alternatively, you could return a tuple that decays the argument types, and avoids the dangling issue, like this:

auto tie = [](const A& a1) {
    return std::make_tuple(a1.a, a1.e1);
};
cigien
  • 57,834
  • 11
  • 73
  • 112
  • 2
    What local variables are being used? `a1` is a reference, – NathanOliver Sep 18 '20 at 14:03
  • 3
    @NathanOliver from [cppreference](https://en.cppreference.com/w/cpp/language/bit_field): "When initializing a const reference from a bit field, a temporary is created (its type is the type of the bit field), copy initialized with the value of the bit field, and the reference is bound to that temporary.". Nasty. – Quentin Sep 18 '20 at 14:05
  • 2
    If you do the comparison at compile time with a `static_assert`, clang produces a really nice error message explaining what happened, [demo here](https://godbolt.org/z/7M1Wxq). – IlCapitano Sep 18 '20 at 14:27