Різний оператор приведення викликається різними компіляторами


80

Розглянемо таку коротку програму на C ++:

#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 більш "дорогим"?

Якщо я видаляю const, усі компілятори однаково видають falseвихідні дані. З іншого боку, якщо я поєднаю два класи разом (обидва оператори будуть в одному класі), усі три компілятори видадуть trueрезультат.


3
Як я зрозумів, B2 має 2 оператори: B :: operator bool () та B2 :: operator int (). Обидва оператори не є операторами const, і існує різниця між версіями const та non-const. Ви можете додати const-версію bool до B та const-версію int до B2 і побачити зміни.
Танукі

1
constтут ключ. Обидва оператори однаково const чи ні, вони поводяться, як очікувалося, boolверсія "виграє". З різною стійкістю, завжди неперервна версія "виграє" на кожній платформі. З похідними класами та різною константністю операторів VS, схоже, має помилку, оскільки вона поводиться інакше, ніж інші два компілятори.
buc

чи можуть різні конструктори за замовчуванням відповідати за різну поведінку?
ldgorman

16
EDG також друкує true. Якщо GCC, Clang та EDG погоджуються, і MSVC не погоджується, що зазвичай означає, що MSVC помиляється.
Джонатан Уейклі

1
Примітка: загалом у C ++ тип повернення не бере участі у дозволі перевантаження.
Matthieu M.

Відповіді:


51

Стандарт зазначає:

Функція перетворення в похідному класі не приховує функції перетворення в базовому класі, якщо ці дві функції не перетворюються на один і той же тип.

§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]

Тому значення inta може бути перетворено в a bool, а a також boolможе бути перетворено в a bool.

Стандарт зазначає:

Розглянуто функції перетворення Sта його базові класи. Ті невідверті функції перетворення, які не приховані всередині Sі видають тип, T або тип, який можна перетворити в тип Tза допомогою стандартної послідовності перетворення, є функціями-кандидатами.

§13.3.1.5 [over.match.conv]

Отже, набір перевантажень складається з operator intта operator bool. За інших рівних operator intумов кращий збіг (оскільки вам не потрібно додавати стійкість). Тому operator intслід обирати.

Зверніть увагу, що (можливо, проти інтуїції) стандарт не враховує тип повернення (тобто тип, до якого ці оператори перетворюють), як тільки вони були додані до набору перевантажень (як встановлено вище), за умови послідовності перетворення для аргументів одного з вони перевершують послідовність перетворень для аргументів іншої (що, внаслідок стійкості, має місце в даному випадку).

Стандарт зазначає:

З огляду на ці визначення, життєздатну функцію F1 визначено кращою, ніж інша життєздатна функція F2, якщо для всіх аргументів i 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
Ви можете отримати N3337, який (я вважаю) був першим проектом після C ++ 11, і він включає лише дуже-дуже незначні зміни тут безкоштовно. Що стосується пошуку належної частини стандарту, то це, як правило, питання оцінки розділів PDF, вивчення того, де що знаходиться (шляхом пошуку речей / цитування стандарту), і пошук відповідних ключових слів за допомогою CTRL + F.
Роберт Аллан Хенніган Ліхі

1
@ikh Де я можу знайти поточні стандартні документи C або C ++? це найкраще питання для цього, і, наскільки мені відомо, вони охоплюють усі проекти, доступні як для C, так і для C ++.
Шафік Ягмор

2
Ось що я сказав із "застосовується лише після того, як визначити, яка послідовність перетворення є кращою". Тим не менше, я думаю, що це важлива частина відповіді: існує (теоретично) конфлікт між перетворенням аргументу мається на увазі об'єкт та "перетворенням типу повернення", і це однозначно вирішується різними етапами вирішення перевантаження. [over.match.best] /1.4 чітко вказує на поділ цих кроків і на те, чому остаточна стандартна послідовність перетворення в цьому випадку не має значення.
dyp

2
Я відредагував відповідь, щоб включити вашу пропозицію, деталізуючи, чому тип повернення цих операторів перетворення не враховується при виборі "найкращого".
Роберт Аллан Хенніган Ліхі,

2
@RobertAllanHenniganLeahy Оскільки це була суть питання, чи не повинно це з’являтися десь у відповіді, а не просто коментарі?
Barmar

9

Короткий

Функція перетворення operator int()вибирається clang over, operator bool() constоскільки bне є const, тоді як оператор перетворення для bool є.

Короткі міркування полягають у тому, що кандидат функціонує для дозволу перевантаження (із наявним неявним параметром об’єкта) під час перетворення bна boolє

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

де другий - кращий матч, оскільки bне відповідає кваліфікації.

Якщо обидві функції мають однакову кваліфікацію (обидві constчи ні), operator boolвибрано, оскільки вона забезпечує пряме перетворення.

Перетворення за допомогою нотації, аналізується поетапно

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

1. Акторський вираз

Акторський склад `` b ''

(bool)b

оцінює до

static_cast<bool>(b)

відповідно до C ++ 11, 5.4 / 4 [expr.cast], оскільки const_castне застосовується (тут не додавання або видалення const).

Це статичне перетворення допускається за C ++ 11, 5.2.9 / 4 [expr.static.cast] , якщо bool t(b);для винайденої змінної t добре сформовано. Такі оператори називаються прямою ініціалізацією відповідно до C ++ 11, 8.5 / 15 [dcl.init] .

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

Пункт 16 найменш згаданого стандартного абзацу говорить (наголошую на моєму):

Семантика ініціалізаторів така. Тип призначення - це тип об’єкта або посилання, що ініціалізується, а тип джерела - тип виразу ініціалізатора.

[...]

[...] якщо тип джерела є типом класу (можливо, відповідає стандарту cv) , розглядаються функції перетворення .

Перелічені відповідні функції перетворення, і найкраща з них вибирається за допомогою дозволу перевантаження.

2.1 Які функції перетворення доступні?

Доступні функції перетворення є, operator int ()і operator bool() constоскільки, як говорить C ++ 11, 12.3 / 5 [class.conv] :

Функція перетворення в похідному класі не приховує функції перетворення в базовому класі, якщо ці дві функції не перетворюються на один і той же тип.

Хоча C ++ 11, 13.3.1.5/1 [over.match.conv] говорить:

Розглянуто функції перетворення S та його базових класів.

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

2.2 Які функції перетворення застосовні?

C ++ 11, 13.3.1.5/1 [over.match.conv] (курсив мій):

1 [...] Припускаючи, що “cv1 T” - це тип об’єкта, який ініціалізується, а “cv S” - тип виразу ініціалізатора, при цьому тип класу S, функції-кандидати вибираються наступним чином: Перетворення розглядаються функції S та його базових класів. Ті невідверті функції перетворення, які не приховані в S і дають тип T або тип, який можна перетворити в тип T за допомогою стандартної послідовності перетворення, є функціями-кандидатами.

Тому operator bool () constзастосовується, оскільки він не прихований всередині B2і дає a bool.

Частина з наголосом в останній стандартній цитаті є релевантною для перетворення з використанням, operator int ()оскільки intце тип, який можна перетворити на bool за допомогою стандартної послідовності перетворення. Перетворення з intу boolє навіть не послідовністю, а простим прямим перетворенням, яке дозволено на C ++ 11, 4.12 / 1 [conv.bool]

Перше значення арифметики, неперерахованого перерахування, покажчик або покажчик на тип члена може бути перетворено на перше значення типу bool. Нульове значення, значення нульового покажчика або значення покажчика нульового члена перетворюється на false; будь-яке інше значення перетворюється на істинне.

Це означає, що operator int ()це також застосовується.

2.3 Яка функція перетворення обрана?

Вибір відповідної функції перетворення здійснюється за допомогою дозволу перевантаження ( C ++ 11, 13.3.1.5/1 [over.match.conv] ):

Роздільна здатність перевантаження використовується для вибору функції перетворення, яку потрібно викликати.

Існує одна особлива "химерність", коли справа стосується дозволу перевантаження для функцій члена класу: неявний параметр об'єкта ".

За C ++ 11, 13.3.1 [over.match.funcs] ,

[...] як статичні, так і нестатичні функції-члени мають неявний параметр об’єкта [...]

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

  • “Посилання на значення cv X” для функцій, оголошених без кваліфікатора ref або з кваліфікатором & ref

  • “Посилання rvalue на cv X” для функцій, оголошених за допомогою && ref-кваліфікатора

де X - клас, членом якого є функція, а cv - кваліфікація cv у оголошенні функції-члена.

Це означає, що (згідно C ++ 11, 13.3.1.5/2 [over.match.conv] ), при ініціалізації функцією перетворення,

[t] Список аргументів має один аргумент, який є виразом ініціалізатора. [Примітка: Цей аргумент буде порівняно з неявним параметром об’єкта функцій перетворення. —Кінець примітки]

Функціями-кандидатами для вирішення перевантаження є:

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

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

Якщо обидві функції перетворення мають однакову кваліфікацію const, дозвіл на перевантаження цієї функції більше не зробить фокусу. У такому випадку ранжування конверсії (послідовності) стає на місце.

3. Чому operator bool ()вибрано, коли обидві функції перетворення мають однакову кваліфікацію const?

Перетворення з B2у boolє визначеною користувачем послідовністю перетворення ( C ++ 11, 13.3.3.1.2 / 1 [over.ics.user] )

Визначена користувачем послідовність перетворення складається з початкової стандартної послідовності перетворення, за якою слідує перетворення, визначене користувачем, за яким слід друга стандартна послідовність перетворення.

[...] Якщо визначена користувачем перетворення задається функцією перетворення, початкова стандартна послідовність перетворення перетворює тип джерела на неявний параметр об’єкта функції перетворення.

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

[...] визначає часткове впорядкування неявних послідовностей перетворень на основі взаємозв’язків кращої послідовності перетворення та кращого перетворення.

... друга стандартна послідовність перетворення U2.

Друге стандартне перетворення є випадком operator bool()is boolдо bool(перетворення ідентичності), тоді як друге стандартне перетворення у випадку operator int ()є, intдо boolякого є булевим перетворенням.

Отже, послідовність перетворення, використовуючи operator bool (), є кращою, якщо обидві функції перетворення мають однакову кваліфікацію const.


У більшості випадків досить інтуїтивно зрозуміло, що те, що ви робите з результатом, не впливає на спосіб його обчислення. Ми обчислюємо a/bоднаково, чи є код float f = a/b;чи int f = a/b;. Але це випадок, коли це може трохи здивувати.
Девід Шварц,

1

Тип bool C ++ має два значення - 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, проблема вирішується.


8
Це приємне рішення для написання програми, але воно не дає відповіді, чому ця дивна річ відбувається детально.
ikh

4
true and false with corresponding values 1 and 0Це надто спрощення. trueперетворює в ціле число 1, але це не означає, що воно "має" значення 1. Дійсно, trueможе бути 42внизу.
Гонки легкості на орбіті

Наявність явного оператора bool у B2 дозволяє уникнути плутанини та залежностей компілятора того, що викликається у B2, оператора int або оператора bool.
Доктор Дебасіш Яна

Немає залежності від компілятора. Стандарт вимагає, щоб 0 перетворювало на false і 1 перетворювало на true.
Щеня

1
@LightnessRacesinOrbit До речі, можливо: a boolніколи не має значення 0 або 1; єдиними значеннями, які він може прийняти, є falseі true(які перетворюються на 0 та 1, якщо boolперетворено на інтегральний тип).
James Kanze

0

Деякі попередні відповіді вже містять багато інформації.

Мій внесок полягає в тому, що "операції приведення" компілюються подібно до "перевантажених операцій", я пропоную створити функцію з унікальним ідентифікатором для кожної операції, а пізніше, замінити її необхідним оператором або приводом.

#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 центи.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.