clang жалуется на функцию constexpr в случае оператора switch

struct X
    {
    enum class E
    {
        A,B
    };

    static constexpr X A()
    {
        return X{E::A};
    }

    static constexpr X B()
    {
        return X{E::B};
    }

    constexpr operator E() const
    {
        return a;
    }
    E a;
};

template <typename T>
struct Y
{
    void f()
    {
        // without this line clang errs
        // const auto & x = this->x;
        switch(x)
        {
            case X::A():
            case X::B():
            default: return;
        }
    }

    X x = X::A();
};

int main()
{
    Y<int>{}.f();
}

Без отмеченной строки во фрагменте лязг выдает следующую ошибку:

ошибка: значение регистра не является константой выражения

X :: B():

Однако я попробовал gcc, и он скомпилирован нормально. Кто-нибудь знает, мягко ли gcc или у clang есть какая-то ошибка?

Смотрите на godbolt (clang 8.0.0): https://godbolt.org/z/ETe5WQ Однако (gcc 8.3) прекрасно компилируется (также на godbolt) и пробовал другие версии gcc и тоже отлично

Обновить:

открыл ошибку

Ответы

Ответ 1

Clang (8.0.0) имеет ошибку здесь.

Если вы напишите constexpr auto A = X::A(); и используйте case A: в вашем операторе switch вы получаете ту же ошибку компиляции (говоря, что A не является константным выражением).

Однако, если вы удалите эти случаи, это скомпилирует нормально (что означает, что A является допустимым constexpr => противоречием предыдущей ошибке).

Кроме того, switch(x) завершается ошибкой, в то время как switch(this->x) завершается успешно. Поскольку x == this->x в вашем случае, это определенно ошибка.

Как упомянуто chtz, clang (5/6), кажется, работает очень хорошо. Это не аргумент, а очевидный регресс.

Обновление: как упомянуто ФП, они подали отчет об ошибке.

Ответ 2

Похоже, что Clang не работает, что switch(x) является переключателем на перечислении X::E

Если вы добавите явное приведение к X::E (static_cast или C-style или что-то еще), ваш код компилируется без вашего изменения.

Это происходит только тогда, когда ваш класс является template.

Использование switch(this->x) также работает.

Поскольку всякий раз, когда x является членом класса, x - это просто другое имя для this->x даже в template, это должно быть ошибкой лягушки.

Правила того, как вы можете сделать переключение для не перечислимого/целочисленного типа, интересны тем, что они основаны на существовании неопределенного оператора приведения к любому перечисляемому или целочисленному типу в выражении switch, а затем вызывают то же приведение в case