Различные операторы литья, вызываемые разными компиляторами

Рассмотрим следующую короткую программу на С++:

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Если я скомпилирую его на разных компиляторах, я получаю различные результаты. С Clang 3.4 и GCC 4.4.7 он печатает true, а Visual Studio 2013 печатает false, что означает, что они вызывают разные операторы трансляции в (bool)b. Какое правильное поведение соответствует стандарту?

В моем понимании operator bool() не требуется преобразование, а operator int() требуется преобразование int в bool, поэтому компилятор должен выбрать первый. Делает ли const что-то с этим, const-conversion считается более "дорогим" компилятором?

Если я удаляю const, все компиляторы одинаково производят false как вывод. С другой стороны, если я объединю два класса вместе (оба оператора будут в одном классе), все три компилятора произведут вывод true.

Ответ 1

Стандартные состояния:

Функция преобразования в производном классе не скрывает функцию преобразования в базовом классе, если две функции не преобразуются в один и тот же тип.

§12.3 [class.conv]

Это означает, что operator bool не скрывается operator int.

Стандартные состояния:

Во время разрешения перегрузки подразумеваемый аргумент объекта неотличим от других аргументов.

§13.3.3.1 [over.match.funcs]

"подразумеваемый аргумент объекта" в этом случае равен b, который имеет тип B2 &. operator bool требует const B2 &, поэтому компилятору нужно будет добавить const в b для вызова operator bool. Это - при прочих равных условиях - делает operator int лучшим совпадением.

В стандарте указано, что a static_cast (который выполняется в C-стиле в этом экземпляре) может преобразовать в тип T (в данном случае int), если:

декларация T t(e); является корректной, для некоторой изобретенной временной переменной T.

§5.2.9 [expr.static.cast]

Следовательно, int может быть преобразован в bool, а a bool можно в равной степени преобразовать в bool.

Стандартные состояния:

Рассматриваются функции преобразования S и его базовые классы. Те функции неявного преобразования, которые не скрыты в S и тип yield T , или тип, который может быть преобразован в тип T через стандартную последовательность преобразования, являются кандидатными функциями.

§13.3.1.5 [over.match.conv]

Таким образом, набор перегрузки состоит из operator int и operator bool. При прочих равных условиях operator int лучше сочетается (поскольку вам не нужно добавлять константу). Поэтому следует выбрать operator int.

Обратите внимание, что (возможно, против интуиции) стандарт не учитывает тип возврата (т.е. тип, к которому эти операторы конвертируют), как только они были добавлены в набор перегрузки (как установлено выше), при условии, что последовательность преобразований для аргументов одного из них превосходит последовательность преобразования для аргументов другой (которая из-за константы имеет место в этом случае).

Стандартные состояния:

Учитывая эти определения, жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если для всех аргументов я ICSi (F1) не является худшей последовательностью преобразования, чем ICSi (F2), а затем

  • для некоторого аргумента j, ICSj (F1) является лучшей последовательностью преобразования, чем ICSj (F2), или, если не это,
  • контекст представляет собой инициализацию по пользовательскому преобразованию и стандартную последовательность преобразования из возвращаемого типа F1 в тип назначения (т.е. тип инициализируемого объекта) является лучшей последовательностью преобразования, чем стандартная последовательность преобразования из тип возврата F2 к типу назначения.

§13.3.3 [over.match.best]

В этом случае существует только один аргумент (неявный this параметр). Последовательность преобразования для B2 & = > B2 & (для вызова operator int) превосходит B2 & = > const B2 & (для вызова operator bool), и поэтому operator int выбирается из набора перегрузки без что он фактически не преобразуется непосредственно в bool.

Ответ 2

Короткие

Функция преобразования operator int() выбирается clang над operator bool() const, так как b не является константной, тогда как оператор преобразования для bool является.

Короткий аргумент состоит в том, что кандидат выполняет функции разрешения перегрузки (с неявным параметром объекта), при преобразовании b в bool используются

operator bool (B2 const &);
operator int (B2 &);

где второе - лучшее совпадение, так как b не является константным.

Если обе функции имеют одну и ту же квалификацию (либо const, либо нет), выбирается operator bool, поскольку она обеспечивает прямое преобразование.

Преобразование с помощью буквенных обозначений, шаг за шагом проанализировано

Если мы согласны с тем, что булевский вставщик ввода (std:: basic_ostream:: operator < < (bool val) в соответствии с [ostream.inserters.arithmetic]) вызывается со значением, которое является результатом преобразования b до bool мы можем преобразовать это преобразование.

1. Выраженное выражение

Листинг b для bool

(bool)b

оценивается как

static_cast<bool>(b)

согласно С++ 11, 5.4/4 [expr.cast], поскольку const_cast неприменим (не добавляя или не удаляя здесь const).

Это статическое преобразование разрешено для С++ 11, 5.2.9/4 [expr.static.cast], если bool t(b); для изобретенной переменной t хорошо сформирована. Такие операторы называются прямой инициализацией в соответствии с С++ 11, 8.5/15 [dcl.init].

2. Прямая инициализация bool t(b);

Пункт 16 наименее упомянутых стандартных положений пункта (внимание мое):

Семантика инициализаторов такова. Тип назначения - тип инициализированного объекта или ссылки, а тип источника - тип выражения инициализатора.

[...]

[...], если тип источника является классом класса (возможно, с квалификацией cv), рассматриваются функции преобразования.

Применяемые функции преобразования перечисляются, а лучший выбирается с помощью разрешения перегрузки.

2.1 Какие функции преобразования доступны?

Доступные функции преобразования operator int () и operator bool() const, так как С++ 11, 12.3/5 [class.conv] сообщает нам:

Функция преобразования в производном классе не скрывает функцию преобразования в базовом классе, если две функции не преобразуются в один и тот же тип.

В то время как С++ 11, 13.3.1.5/1 [over.match.conv] указывает:

Рассматриваются функции преобразования S и его базовые классы.

где S - класс, который будет преобразован из.

2.2 Какие функции преобразования применимы?

С++ 11, 13.3.1.5/1 [over.match.conv] (выделено мной):

1 [...] Предполагая, что "cv1 T" является типом инициализированного объекта, а "cv S" является типом выражения инициализатора, с S-типом класса, кандидатные функции выбираются следующим образом: Рассматриваются функции преобразования S и его базовые классы. Неявные функции преобразования, которые не скрыты внутри S и тип вывода T , или тип, который может быть преобразован в тип T с помощью стандартной последовательности преобразования, являются кандидатными функциями.

Следовательно, operator bool () const применим, поскольку он не скрыт внутри B2 и дает bool.

Часть с акцентом в последней стандартной цитате применима для преобразования с использованием operator int (), поскольку int - это тип, который можно преобразовать в bool через стандартную последовательность преобразования. Преобразование из int в bool - это даже не последовательность, а простое прямое преобразование, разрешенное для С++ 11, 4.12/1 [conv.bool]

Значение арифметики, неперечисленное перечисление, указатель или указатель на тип члена может быть преобразовано в prvalue типа bool. Значение нуля, значение нулевого указателя или значение указателя нулевого элемента преобразуется в значение false; любое другое значение преобразуется в значение true.

Это означает, что operator int () также применим.

2.3 Какая функция преобразования выбрана?

Выбор соответствующей функции преобразования выполняется с помощью разрешения перегрузки (С++ 11, 13.3.1.5/1 [over.match.conv]):

Разрешение перегрузки используется для выбора функции преобразования, которую нужно вызвать.

Существует одна специальная "причуда", когда речь идет о разрешении перегрузки для функций-членов класса: параметр неявного объекта ".

Per С++ 11, 13.3.1 [over.match.funcs],

[...] как статические, так и нестатические функции-члены имеют неявный параметр объекта [...]

где тип этого параметра для нестатических функций-членов - согласно пункту 4:

  • "lvalue reference to cv X" для функций, объявленных без ref-qualifier или с рефлектором-определителем

  • "rvalue reference to cv X" для функций, объявленных с помощью && & & реф-классификатор

где X - класс, членом которого является член, а cv - это cv-квалификация в объявлении функции-члена.

Это означает, что (за С++ 11, 13.3.1.5/2 [over.match.conv]), при инициализации с помощью функции преобразования,

[t] список аргументов имеет один аргумент, который является выражением инициализатора. [Примечание. Этот аргумент будет сравниваться с неявным параметром объекта функций преобразования. -end note]

Функции-кандидаты для разрешения перегрузки:

operator bool (B2 const &);
operator int (B2 &);

Очевидно, что operator int () является лучшим совпадением, если запрашивается преобразование с использованием непостоянного объекта типа B2, так как operator bool () требуется преобразование квалификации.

Если обе функции преобразования имеют одну и ту же константную квалификацию, разрешение перегрузки этой функции больше не будет делать трюк. В этом случае начинается преобразование (последовательность).

3. Почему operator bool () выбран, когда обе функции преобразования имеют одну и ту же константную квалификацию?

Преобразование из B2 в bool является пользовательской последовательностью преобразования (С++ 11, 13.3.3.1.2/1 [over.ics.user])

Пользовательская последовательность преобразования состоит из начальной стандартной последовательности преобразования, за которой следует пользовательское преобразование, за которым следует вторая стандартная последовательность преобразования.

[...] Если определяемое пользователем преобразование задается функцией преобразования, начальная стандартная последовательность преобразования преобразует тип источника в неявный объектный параметр функции преобразования.

С++ 11, 13.3.3.2/3 [over.ics.rank]

[...] определяет частичный порядок последовательности неявных преобразований, основанный на улучшенной последовательности преобразований и улучшении преобразования.

[...] Пользовательская последовательность U1 преобразования является лучшей последовательностью преобразования, чем другая пользовательская последовательность U2 преобразования, если они содержат одну и ту же пользовательскую функцию преобразования или конструктор или инициализацию агрегата, а вторую стандартную последовательность преобразования U1 лучше, чем вторая стандартная последовательность преобразования U2.

Второе стандартное преобразование - это случай operator bool() составляет от bool до bool (преобразование идентичности), тогда как второе стандартное преобразование в случае operator int () составляет от int до bool, которое является булевым преобразованием.

Следовательно, последовательность преобразования, используя operator bool (), лучше, если обе функции преобразования имеют одну и ту же константную квалификацию.

Ответ 3

Тип bool С++ имеет два значения: true и false с соответствующими значениями 1 и 0. Введенную путаницу можно избежать, если вы добавите оператор bool в класс B2, который явно вызывает оператор bool базового класса (B), затем вывод приходит как ложное. Здесь моя измененная программа. Тогда оператор bool означает оператор bool, а не оператор int любыми способами.

#include <iostream>

class B {
public:
    operator bool() const {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
    operator bool() {
        return B::operator bool();
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

В вашем примере (bool) b пытался вызвать оператор bool для B2, B2 наследовал оператор bool и оператор int, по правилу доминирования, оператор int получил вызов и унаследованный оператор bool в B2. Однако, явно имея оператор bool в самом B2-классе, проблема решается.

Ответ 4

Некоторые из предыдущих ответов уже содержат много информации.

Мой вклад в том, что "операции литья" скомпилированы аналогично "перегруженным операциям", я предлагаю создать функцию с уникальным идентификатором для каждой операции, а затем заменить ее на требуемый оператор или литье.

#include <iostream>

class B {
public:
    bool ToBool() const {
        return false;
    }
};

class B2 : public B {
public:
    int ToInt() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << b.ToBool() << std::endl;
}

И, позднее, примените оператор или бросок.

#include <iostream>

class B {
public:
    operator bool() {
        return false;
    }
};

class B2 : public B {
public:
    operator int() {
        return 5;
    }
};

int main() {
    B2 b;
    std::cout << std::boolalpha << (bool)b << std::endl;
}

Только мои 2 цента.