4

I have a method requiring several variables of the same enum type. To allow the compiler to detect if I pass the wrong argument I am using BOOST_STRONG_TYPEDEF. However, I get a seg fault when I create an instance and compare within an IF statement.

Boost version is 1.74

enum class Testable
{
    UNDEFINED,
    A,
    B
};

BOOST_STRONG_TYPEDEF(Testable, SomeType)

int main()
{  
    SomeType abc{Testable::UNDEFINED};
    std::cout << "START" << std::endl;
    
    if(abc == Testable::UNDEFINED)  // Seg faults here
    {
        volatile int j = 0;
    }
    
    std::cout << "FINISH" << std::endl;
}

GDB backtrace suggests it's a stack overflow/recursive calling:

#1    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:
#2    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:
#3    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:
#4    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:
#5    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:
#6    0x00007ffff74c5d9d in boost::operators_impl::operator== (y=@0x7fffffcc9e44:

There's not much documentation for BOOST_STRONG_TYPEDEF. Am I using it wrong?

Boost version is 1.74. I'm using Clang.

user997112
  • 29,025
  • 43
  • 182
  • 361
  • Since `enum class` itself is a strong type, it's unclear to me why it is necessary to put on some other "strong typedef" on top of it. – Sam Varshavchik Jan 09 '21 at 02:12
  • @SamVarshavchik I have a function processing 4 instances of that one type. They have contextual differences. To prevent passing the wrong instance I am using Boost Strong Type to effectively create a "sub-type". – user997112 Jan 09 '21 at 02:14
  • Hmm... I would just declare four `struct`s containing an instance of this `enum class` as their only member, define a defaulted `==` and `!=` operator, and a constructor that swallows the enum class (may not be necessary, thanks to aggregate initialization in C++whatever). – Sam Varshavchik Jan 09 '21 at 02:21

1 Answers1

1

Sanitizer says

==3044==ERROR: AddressSanitizer: stack-overflow on address 0x7ffcc58b3ff8 (pc 0x56310c340e84 bp 0x7ffcc58b4000 sp 0x7ffcc58b3ff
0 T0)
    #0 0x56310c340e84 in boost::operators_impl::operator==(Testable const&, SomeType const&) /home/sehe/custom/boost_1_75_0/boo

The problem is that Boost's STRONG_TYPEDEF makes the derivative type totally-ordered with the base type:

struct SomeType
    : boost::totally_ordered1<SomeType, boost::totally_ordered2<SomeType, Testable>>
{
    Testable t;
    explicit SomeType(const Testable& t_) noexcept((boost::has_nothrow_copy_constructor<Testable>::value)) : t(t_) {}
    SomeType() noexcept( (boost::has_nothrow_default_constructor<Testable>::value)) : t() {}
    SomeType(const SomeType& t_) noexcept( (boost::has_nothrow_copy_constructor<Testable>::value)) : t(t_.t) {}
    SomeType& operator=(const SomeType& rhs) noexcept((boost::has_nothrow_assign<Testable>::value)) {
        t = rhs.t;
        return *this;
    }
    SomeType& operator=(const Testable& rhs) noexcept((boost::has_nothrow_assign<Testable>::value)) {
        t = rhs;
        return *this;
    }
    operator const Testable&() const { return t; }
    operator Testable&() { return t; }
    bool operator==(const SomeType& rhs) const { return t == rhs.t; }
    bool operator<(const SomeType& rhs) const { return t < rhs.t; }
};

If you remove that source of implicit conversion:

struct SomeType
    : boost::totally_ordered1<SomeType
      /*, boost::totally_ordered2<SomeType, Testable>*/>
{
     // ...

it JustWorks(TM). I would argue that you should make the conversion operators explicit as well and always do the casts:

Live On Coliru

#include <boost/serialization/strong_typedef.hpp>
#include <iostream>

enum class Testable { UNDEFINED, A, B };
       
struct SomeType
    : boost::totally_ordered1<SomeType
      /*, boost::totally_ordered2<SomeType, Testable>*/>
{
    Testable t;
    explicit SomeType(const Testable& t_) noexcept((boost::has_nothrow_copy_constructor<Testable>::value)) : t(t_) {}
    SomeType() noexcept( (boost::has_nothrow_default_constructor<Testable>::value)) : t() {}
    SomeType(const SomeType& t_) noexcept( (boost::has_nothrow_copy_constructor<Testable>::value)) : t(t_.t) {}
    SomeType& operator=(const SomeType& rhs) noexcept((boost::has_nothrow_assign<Testable>::value)) {
        t = rhs.t;
        return *this;
    }
    SomeType& operator=(const Testable& rhs) noexcept((boost::has_nothrow_assign<Testable>::value)) {
        t = rhs;
        return *this;
    }
    explicit operator const Testable&() const { return t; }
    explicit operator Testable&() { return t; }
    bool operator==(const SomeType& rhs) const { return t == rhs.t; }
    bool operator<(const SomeType& rhs) const { return t < rhs.t; }
};

int main() {
    SomeType abc{ Testable::UNDEFINED };
    std::cout << "START" << std::endl;

    if (abc == SomeType{Testable::UNDEFINED}) {
        volatile int j = 0;
    }

    std::cout << "FINISH" << std::endl;
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Hi, thanks for this. I'm not massively familiar with total order. I Google'd and it seems to be an interface to force implementing the equality and inequality operator? Why does this cause the recursion? I prefer explicit casting, given the whole point of using strong typedef. – user997112 Jan 09 '21 at 17:25
  • 1
    The good news is: you can just `explicit` all the constructors and conversion operators. The bad new is it does look lile there's a change in behaviour since c++20 that causes this to "miscompile" (it's correct code-gen but differs from c++17 behaviour): https://i.imgur.com/flcj4Cs.png This could be reportable to the Boost Operator devs – sehe Jan 09 '21 at 23:07
  • 1
    Reduced this to a new question and asked https://stackoverflow.com/questions/65648897/c20-behaviour-breaking-existing-code-with-equality-operator – sehe Jan 10 '21 at 00:10
  • 1
    Oh, removing the `totally_ordered2` base class does still resolve the issue - in case you were uncertain. I find the problem interesting on principle, but that should not worry you too much :) – sehe Jan 10 '21 at 00:21
  • 1
    Relevant existing library issue: https://github.com/boostorg/utility/issues/65, also has a ton of context – sehe Jan 10 '21 at 01:07
  • So it's to do with the ordering of choosing overloaded operators? – user997112 Jan 11 '21 at 00:37
  • I ended up doing what you suggested and wrote my own (using your's). However, what overload would you recommend to retrieve the encapsulated member? – user997112 Jan 21 '21 at 19:25
  • 1
    @user997112 I'm ok with the `explicit` conversion operator https://en.cppreference.com/w/cpp/language/cast_operator – sehe Jan 21 '21 at 20:05