关于C++:g++和Clang+++与操作符<() 的不同行为

g++ and clang++ different behaviour with operator<() overloading

你好,抱歉我英语不好。

为了用C++ 11来实践,我尝试编写一个版本的STD::实验::任何(HTTP://E.CPAPYCENCE.COM/W/CPP/实验Altal/An)添加一些额外的东西。

添加操作符<(),我在g++(4.9.2)和clang++(3.5.0)之间得到了不同的行为。

下面是类(以及使用的类)的一个简短版本,涵盖了所需的最小值,以及一个非常小的main()来触发问题。

抱歉,代码太长,但我未能缩短示例的长度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include <memory>
#include <iostream>
#include <type_traits>
#include <unordered_set>

namespace yans // yet another name space
 {
   class anyB  // base for any
    {
      public:

         virtual std::type_info const & typeT () const = 0;
         virtual bool isLess (anyB const *) const = 0;
    };

   template <typename T>
      class anyD : public anyB  // derived for any
       {
         private:

            T val;

            static std::type_info const & typeInfo ()
             { static auto const & ret = typeid(T); return ret; }

            template <typename U> // preferred version
               static auto lessF (U const & u1, U const & u2, int)
               -> decltype(  std::declval<U const &>()
                           < std::declval<U const &>())
                { return (u1 < u2); }

            template <typename U> // emergency version
               static auto lessF (U const &, U const &, ...) -> bool
                { throw std::runtime_error("no operator < for type"); }

         public:

            anyD (T const & v0)
               : val(v0)
                { }

            std::type_info const & typeT () const override final
             { return typeInfo(); }

            bool isLess (anyB const * pB0) const override final
             {
               auto pD0 = dynamic_cast const *>(pB0);

               if ( nullptr == pD0 )
                  throw std::bad_cast();

               return lessF(val, pD0->val, 0);
             }
       };

   class any
    {
      private:
         template <class T>
            using sT = typename std::decay<T>::type;

         template <class T>
            using noAny
            = typename std::enable_if
            <false == std::is_same>::value, bool>::type;

         template <class T>
            using isCpCtr
            = typename std::enable_if
            <true == std::is_copy_constructible<sT<T>>::value,bool>::type;

         std::unique_ptr  ptr;

         static std::type_info const & voidInfo ()
          { static auto const & ret = typeid(void); return ret; }

         bool opLess (any const & a0) const
          {
            return
                  type().before(a0.type())
               || (   (type() == a0.type())
                   && (false == empty())
                   && ptr.get()->isLess(a0.ptr.get()) );
          }

      public:

         template <typename T, typename = noAny<T>, typename = isCpCtr<T>>
            any (T && v0)
            : ptr(new anyD<sT<T>>(std::forward<T>(v0)))
             { }

         bool empty () const noexcept
          { return ! bool(ptr); }

         std::type_info const & type () const
          { return ( ptr ? ptr->typeT() : voidInfo()); }

         friend bool operator< (any const &, any const &);
    };

   bool operator< (any const & a0, any const & a1)
    { return a0.opLess(a1); }
 }

int main ()
 {
   try
    {
      yans::any  ai { 12 };
      yans::any  as { std::string("t1") };
      yans::any  au { std::unordered_set<int> { 1, 5, 3 } };

      std::cout <<"ai < 13 ?" << (ai < 13) << '
'
;
      std::cout <<"as < std::string {"t0"} ?"
         << (as < std::string {"t0"}) << '
'
;
      std::cout <<"au < std::unordered_set<int> { 2, 3, 4 } ?"
         << (au < std::unordered_set<int> { 2, 3, 4 }) << '
'
;
    }
   catch ( std::exception const & e )
    {
      std::cerr <<"
main(): standard exception of type "
"
         << typeid(e).name() <<"
"
"

         <<"  --->" << e.what() <<" <---

"
;
    }

   return EXIT_SUCCESS;
 }

如果左操作数的类型小于右操作数的类型(根据typeid(t).before()),则在运算符<()后面的想法是返回"true";如果类型匹配,则返回所包含值的比较返回的值。我知道这是一个有问题的解决方案,但我在努力学习。

问题是,在类的实例中,any可以包含不带运算符<()的类型的值。在该示例中,std::unordered_set类的一个实例。然后我尝试开发了两个重载(sfinae)方法lessf();首选方法是,当包含类型t的运算符<()可用时,返回比较值;当运算符<()不可用时使用的紧急版本引发异常。

使用clang++,我得到了我想要的:类anyd>不实现lessf的首选版本,比较从紧急版本生成异常。

与G++相反,类anyd>在任意两个实例上生成调用运算符<()的优先版本,构建在lessf()的两个std::unordered_set参数(any的模板化构造函数没有"显式"定义)上,然后递归地调用自身,进入循环并生成错误(即"分段错误")。

我想了解的是:

  • 根据ISO C++ 11,CLAN++的行为或G++的行为是正确的吗?

  • 我可以在本地(仅在lessf()中)并且不声明任何()的模板构造函数"explicit"的情况下,防止两个std::unordered_set之间的比较在两个any之间发生冲突吗?换言之:如何防止在anyd>中开发lessf()的优先版本?

以下是两个程序的输出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
---- clang++ program output ----
ai < 13 ? 1
as < std::string {"t0"} ? 0
au < std::unordered_set<int> { 2, 3, 4 } ?
main(): standard exception of type"St13runtime_error"
  ---> no operator < for type  <---

---- end output ----

---- g++ program output ----
ai < 13 ? 1
as < std::string {"t0"} ? 0
Errore di segmentazione
---- end output ----

我相信你在GCC中发现了一个bug(我在这里以更短的格式复制了它,请继续关注)。

问题是,如果您仔细查看分段错误,您会发现operator<unordered_set的调用是无限递归的。这是因为GCC实际上认为bool operator<(const any&, const any&)是一个匹配项。它不应该在你所说的地方。

简单的解决方法是简单地确保只为any找到operator<(const any&, const any&),而不管您在哪个命名空间中。只需将定义移动到类中:

1
2
3
4
5
class any {
    friend bool operator< (any const & a0, any const & a1) {
        return a0.opLess(a1);
    }
};

不管怎样,这是一个很好的练习。