使用BOOST_STRONG_TYPEDEF区分参数类型但导致段错误

5

我有一个方法需要多个相同枚举类型的变量。为了让编译器检测到我是否传递了错误的参数,我使用了BOOST_STRONG_TYPEDEF。但是,在创建一个实例并在IF语句中进行比较时,我会遇到段错误。

Boost版本为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回溯提示这是堆栈溢出/递归调用问题:

#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:

关于 BOOST_STRONG_TYPEDEF 的文档不多,我用错了吗?

Boost 版本是 1.74。我在用 Clang。


由于 enum class 本身就是强类型,我不清楚为什么需要在其上面再加上其他的“强类型定义”。 - Sam Varshavchik
@SamVarshavchik 我有一个处理同一类型的4个实例的函数。它们有上下文差异。为了防止传递错误的实例,我使用Boost Strong Type有效地创建了一个“子类型”。 - user997112
嗯...我会声明四个struct,其中包含一个enum class实例作为它们唯一的成员,定义默认的==!=运算符,以及一个构造函数来接收该枚举类(在C++中可能不需要,因为有聚合初始化)。 - Sam Varshavchik
1个回答

2

消毒器说

==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

问题在于Boost的STRONG_TYPEDEF使得派生类型与基础类型完全有序:
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; }
};

如果您删除了这个隐式转换的源代码:
struct SomeType
    : boost::totally_ordered1<SomeType
      /*, boost::totally_ordered2<SomeType, Testable>*/>
{
     // ...

它只是工作(TM)。我认为您应该将转换运算符explicit化,并始终进行转换:

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

1
好消息是:您可以将所有构造函数和转换运算符都显式声明为explicit。坏消息是,自从C++20以来,似乎有一种行为变化导致了这种“编译错误”(它的代码生成是正确的,但与C++17的行为不同):https://i.imgur.com/flcj4Cs.png 这可能需要向Boost Operator开发人员报告。 - sehe
1
将此问题简化并询问 https://dev59.com/-FEG5IYBdhLWcg3wduUf - sehe
1
哦,移除 totally_ordered2 基类仍然可以解决这个问题 - 如果你不确定的话。我觉得这个问题从原则上很有趣,但是这不应该让你太担心 :) - sehe
1
相关的现有库问题:https://github.com/boostorg/utility/issues/65,还有大量的上下文。 - sehe
1
@user997112,我对explicit转换运算符感到满意。https://en.cppreference.com/w/cpp/language/cast_operator - sehe
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接