Неявное преобразование из класса в тип перечисления в условном условном выражении
g++ 4.9.0 принимает следующий код:
enum E { foo };
struct C {
operator E() const { return foo; }
operator E() { return foo; }
};
int main() {
C c;
switch (c) {
case foo: break;
}
}
Но clang 3.4.1 отклоняет его со следующей диагностикой:
12 : error: multiple conversions from switch condition type 'C' to an integral or enumeration type
switch (c)
^ ~
5 : note: conversion to enumeration type 'E'
operator E() const { return foo; }
^
6 : note: conversion to enumeration type 'E'
operator E() { return foo; }
^
Какой из них правильный? Это ошибка clang, ошибка g++, ошибка libstdС++, стандартный дефект или другое? Я сделал что-то глупое?
В коде, вызвавшем этот вопрос, C
есть std::atomic<E>
, а std::atomic<T>::operator T
перегружается на cv-квалификаторах const
и const volatile
.
Оба компилятора принимают E e = c;
, поэтому он кажется чем-то особенным для оператора switch
.
Ответы
Ответ 1
Это разница между С++ 11 и С++ 14; clang правильно принимает его в режиме С++ 14 (-std=c++1y
) и отклоняет его в режиме С++ 11 (-std=c++11
), а gcc неверно, чтобы принять его в режиме С++ 11.
Поведение операторов switch
было изменено бумагой n3323, которая приземлилась после завершения стандарта С++ 11.
[stmt.switch], в С++ 11:
2 - Условие должно быть целочисленного типа, типа перечисления или типа класса, для которого существует одна неявная функция преобразования для целочисленного или перечисляемого типа (12.3). [...]
В n3936 (формулировка на n3323):
2 - Условие должно быть целым типом, типом перечисления или типом класса. Если тип класса, условие контекстно неявно преобразованный (раздел 4) в интегральный или перечисляемый тип.
Контекстное неявное преобразование является вариантом неявного преобразования (т.е. требуется декларация T t = e
); для корректного формирования контекстного неявного преобразования тип класса E
допускается иметь несколько функций преобразования, но все допустимые в контексте должны иметь одинаковый тип возврата по модулю cv и ссылочную квалификацию: [conv]
5 - [...] E
выполняется поиск функций преобразования, возвращаемым типом которых является cv T
или ссылка на cv T
такая, что T
разрешается контекстом. Там должно быть ровно одно такое T
.
В выражении switch
контекстное неявное преобразование относится к интегральному или перечисляемому типу, поэтому C
должна иметь хотя бы одну функцию преобразования не explicit
в cv интегральный или тип перечисления или ссылку на cv интеграл или перечисление type и все его функции преобразования в cv интегральный или тип перечисления или ссылку на cv integer или тип перечисления должны иметь тот же базовый тип.
Довольно приятное обходное решение (как упоминалось в n3323) заключается в использовании унарного плюса для принуждения аргумента оператора switch
к арифметическому типу:
switch (+c) {
// ...
Ответ 2
Я считаю, что clang
здесь верен, в зависимости от того, какая версия стандарта используется. Обычно я использую N3485 как ссылку на С++ 11 после исправления, но можно утверждать, что изменение, которое я заметил в Классы с операторами преобразования шаблонов и без шаблонов в состоянии оператора switch являются добавлением и, следовательно, фактически являются частью С++ 1y.
Итак, соглашаемся с тем, что контекстно-неявные преобразования являются добавлением, тогда clang
является правильным для проекта стандарта С++ 11. Из-за раздела 6.4.2
Оператор switch, в котором говорится (выделение моего хода):
Условие должно быть целочисленного типа, типа перечисления или тип класса, для которого одна неявная функция преобразования существует интегральный или перечисляемый тип (12.3). [...]
В С++ 1y тогда это должен быть приемлемый код и запуск этого в режиме С++ 1y в clang
, кажется, подтверждает, что это действительно так (видеть его в прямом эфире).
Из черновик С++ 1y раздел 6.4.2
Оператор switch, который включает контекстуально неявное преобразование. В пункте 2 говорится:
Условие должно быть целочисленного типа, типа перечисления или класса тип. Если тип класса, условие контекстуально неявно преобразованный (раздел 4) в интегральный или перечисляемый тип.
Мы можем видеть, что раздел, который нам нужно использовать, это 4
Стандартные преобразования и параграф 5 охватывают эти случаи, он говорит:
Некоторые языковые конструкции требуют преобразования в значение, имеющее один определенного набора типов, соответствующих конструкции. выражение e типа класса E, появляющееся в таком контексте, называется контекстно-неявно преобразованный в заданный тип T и хорошо сформированный тогда и только тогда, когда e может быть неявно преобразован в тип T который определяется следующим образом: E выполняется поиск функций преобразования чей тип возврата является cv T или ссылкой на cv T, так что T разрешено в контексте. Там должно быть ровно одно такое T.