Search code examples
c++boost

Using BOOST_STRONG_TYPEDEF to differentiate arg types but causing seg fault


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.


Solution

  • 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;
    }