使用 clang 编译 libstdc++ 时出现问题

Issue when compiling libstdc++ with clang

我在使用 clang 编译 libstdc++(即 GNU 对 C++ 标准库的实现)时注意到一个问题。 问题是,如果问题得到确认,我应该向谁报告?

移动分配 std::vector<X, A<X>> 且条件为:

时发生
  1. X 的 move-constructor 可能会抛出(尽管我是 赋值 而不是构造);

  2. A<X> 不会在移动分配上传播,源向量和目标向量使用的分配器比较不相等。

一个 MCVE follows (see it live)。不幸的是,它包含一些样板,最重要的部分在评论中指出。

#include <vector>
#include <memory>
#include <type_traits>

struct X {
    X() = default;
    X(const X&) = default;

    // Move constructor might throw
    X(X&&) noexcept(false) = default;

    // Track calls to assignment functions
    X& operator=(const X&) {
        putchar('c'); return *this;
    }
    X& operator=(X&&) noexcept(true) {
        putchar('m'); return *this;
    }
};

unsigned counter = 0;

template <typename T>
struct A : std::allocator<T> {

    template <typename U>
    struct rebind { using other = A<U>; };

    A() : std::allocator<T>(), id(++counter) {}

    // Does not propagate
    using propagate_on_container_move_assignment = std::false_type;

    // Does not always compare equal
    using is_always_equal = std::false_type;
    bool operator ==(const A& o) { return id == o.id; }
    bool operator !=(const A& o) { return id != o.id; }

    unsigned id;
};

int main() {
    std::vector<X, A<X>> a(2), rv(2);
    a = std::move(rv);
}

据我所知,clang++ 默认使用 libstdc++,使用 libc++ 是通过 -stdlib=libc++ 的选择加入。 运行 上面的代码(同样,使用 clang 和 libstdc++ 构建)显示 cc 这意味着 rv 的两个元素被 复制分配 a.

但是,引用 [container.requirements.general]/4, Table 83

"All existing elements of a are either move assigned to or destroyed"

(这一点进一步得到证实 [container.requirements.general]/16, Table 86.)

另一方面,通过将编译器切换到 gcc 或将库切换到 libc++,我们得到 mm,这与上面的引用一致。这也会通过将 X(X&&) 的异常规范更改为 noexcept(true) 而发生 operator =(X&&) 的异常规范似乎无关紧要。

我错过了什么吗?如果不是,我应该向谁报告这个问题?它是 libstdc++ 或 clang。 (我认为这可能并不明显。)我倾向于认为是后者,因为 AFAIK,clang 应该支持 libstdc++ 而不是相反。

(旁注:上面的代码至少可以揭示另一个问题,即从 A 中删除 rebind 会使 clang/libstdc+ + 编译失败而 clang/libc++ 成功。在我看来,在这种情况下,责任被逆转并落在 libstdc++ 上,但这不是我问题的一部分。)

libstdc++ 正在使用 move_if_noexcept for this,它可能不应该使用。错误报告应该交给他们。

其余似乎是由于在如何处理异常规范不匹配的默认成员函数方面存在分歧。这是格式错误的,然后被删除,然后有效。历史见P1286R2

  • Clang 认为 "ill-formed" 然后 "valid",因此对于它编译的版本,move_if_noexcept 将尝试复制。
  • GCC 认为 "ill-formed" 然后 "deleted"。但是定义为默认的已删除移动构造函数会被重载决策忽略,因此移动构造回退到使用默认的 copy 构造函数,这隐式为 noexcept。因此,move_if_noexcept 会认为该类型不可构造并尝试移动。