Initializer_list std::set<my_class> 使用简单的运算符<初始化。 gcc+/标准库中的错误?

Initializer_list initialization of std::set<my_class> with trivial operator<. Bug in gcc+ / standard library?

这就是我的代码的样子

#include <iostream>
#include <set>
using namespace std;

enum Enum_type 
{
    Enum_type_1 = 1,
    Enum_type_2,
    Enum_type_3,
    Enum_type_4
};

class my_class
{
public:

    my_class(Enum_type type) :
        source_type(type)
    {}

    bool operator<(const my_class &another) const;

    Enum_type source_type;
};

bool my_class::operator<(const my_class& another) const
{
    return true;
}

int main()
{
    std::set<my_class> bracers_initialized_set {
        my_class{Enum_type_1},
        my_class{Enum_type_2},
        my_class{Enum_type_3},
        my_class{Enum_type_4}};

    cout<< "bracers_initialized_set.size() " <<  bracers_initialized_set.size() <<endl;
    for (auto my_class_ : bracers_initialized_set) {
        cout << "enum value: " << my_class_.source_type << endl;
    }
    cout<< "bracers_initialized_set.size() " <<  bracers_initialized_set.size() <<endl;

    std::set<my_class> inserted_set;
    if (inserted_set.insert(my_class(Enum_type_1)).second) {
        cout << "inserted_1" << endl;
    }
    if (inserted_set.insert(my_class(Enum_type_2)).second) {
        cout << "inserted_2" << endl;
    }   
    if (inserted_set.insert(my_class(Enum_type_3)).second) {
        cout << "inserted_3" << endl;
    }
    if (inserted_set.insert(my_class(Enum_type_4)).second) {
        cout << "inserted_4" << endl;
    }

    cout<< "inserted_set.size() " <<  inserted_set.size() <<endl;
    for (auto my_class_ : inserted_set) {
        cout << "enum value: " << my_class_.source_type << endl;
    }
    cout<< "inserted_set.size() " <<  inserted_set.size() <<endl;
}

这是输出:

bracers_initialized_set.size() 4
enum value: 2
enum value: 1
bracers_initialized_set.size() 4
inserted_1
inserted_2
inserted_3
inserted_4
inserted_set.size() 4
enum value: 4
enum value: 3
enum value: 2
enum value: 1
inserted_set.size() 4

如您所见,使用 initializer_list 初始化的集合表现不佳(大小与迭代次数不同)。如果我以这种方式实现 operator<:

bool my_class::operator<(const my_class& another) const
{
    return source_type<another.source_type;
}

那么两个例子的输出都是正确的。

但是 initializer_list 初始化和插入输出不应该是相同的,不管 operator< 看起来如何?另外,大小不应该总是反映元素的数量吗?

$ gcc -v

COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.8/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.4-2ubuntu1~14.04' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04) 

~马辛

在标准中规定,在 §25.4/2:

Compare is a function object type (20.9). The return value of the function call operation applied to an object of type Compare, when contextually converted to bool (Clause 4), yields true if the first argument of the call is less than the second, and false otherwise. Compare comp is used throughout for algorithms assuming an ordering relation. It is assumed that comp will not apply any non-constant function through the dereferenced iterator.

(强调我的)而且在§25.4/3:

For all algorithms that take Compare, there is a version that uses operator< instead. That is, comp(*i, *j) != false defaults to *i < *j != false. For algorithms other than those described in 25.4.3 to work correctly, comp has to induce a strict weak ordering on the values.

(强调我的)这意味着,如 §25.4/4 中所述:

The term strict refers to the requirement of an irreflexive relation (!comp(x, x) for all x), and the term weak to requirements that are not as strong as those for a total ordering, but stronger than those for a partial ordering. If we define equiv(a, b) as !comp(a, b) && !comp(b, a), then the requirements are that comp and equiv both be transitive relations:

  • comp(a, b) && comp(b, c) implies comp(a, c)
  • equiv(a, b) && equiv(b, c) implies equiv(a, c) [ Note: Under these conditions, it can be shown that
    • equiv is an equivalence relation
    • comp induces a well-defined relation on the equivalence classes determined by equiv
    • The induced relation is a strict total ordering. — end note ]

您违反了这些要求,因此标准库违反了其保证。