С++, преобразование bool всегда возвращается к неявному преобразованию в void *?

Вопрос: Не подразумеваемые конверсии bool всегда возвращаются к попытке неявного преобразования в void*? (Если такая функция преобразования существует для типа). Если да, то почему?

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

#include <iostream>

class Foo{
public:

    operator void*() const
    {
        std::cout << "operator void*() const" << std::endl;
        return 0;
    }
};

int main()
{
    Foo f;

    if(f)
        std::cout << "True" << std::endl;
    else
        std::cout << "False" << std::endl;

    return 0;
}

Выход этой программы:

operator void*() const
False

была вызвана функция преобразования в void*. Если мы помечаем квалификатор explicit перед функцией преобразования, то неявное преобразование в void* завершится с ошибкой.

Edit: Кажется, много ответов заключается в том, что "нулевые указатели могут быть преобразованы в false". Я понимаю это, мой вопрос касался "если я не могу напрямую вызвать operator bool(), тогда я попробую преобразование в любой указатель".

Ответы

Ответ 1

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

  • целочисленный арифметический тип (char, int и т.д.)
  • арифметический тип с плавающей запятой (float, double, long double)
  • тип указателя (void* принадлежит здесь, но он также может быть const std::vector<Something>*)
  • указатель на функцию (включая указатель на функцию-член)
  • ссылочный тип для любого из вышеперечисленных

Обратите внимание, однако, что должно существовать только одно такое косвенное преобразование. Если возможны два или более преобразования из приведенного выше списка, компилятор столкнется с двусмысленностью и сообщит об ошибке.

Ответ 2

Для нескольких ссылок в стандарте:

  • §6.4.0.4 [stmt.select]

    Значение условия, являющегося выражением, является значением выражение, контекстно преобразованное в bool для операторов, отличных от switch

  • §4.0.4 [conv]

    Некоторые языковые конструкции требуют, чтобы выражение было преобразовано в логическое значение. Выражение e, появляющееся в таком контексте, называется контекстно преобразованным в bool и хорошо сформировано тогда и только тогда, когда декларация bool t(e); хорошо сформирована для некоторой изобретенной временной переменной t.

  • §8.5.17 [dcl.init]

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

  • §8.5.17.7 [dcl.init]

    В противном случае, если тип источника является (возможно, cv-qualit) классом, рассматриваются функции преобразования. Применяемые функции преобразования перечислены (13.3.1.5), а наилучшая выбирается с помощью разрешения перегрузки (13.3). Выбранное пользователем преобразование вызывается для преобразования выражения инициализатора в инициализированный объект. Если преобразование не может быть выполнено или неоднозначно, инициализация плохо сформирована.

  • §13.3.1.5 [over.match.conv]

    Предполагая, что "cv1 t" является типом инициализированного объекта, а "cv S" является типом выражения инициализатора, причем S a тип класса, выбранные функции выбираются следующим образом:

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

  • §4.13.1 [conv.bool]

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

Ответ 3

Что действительно происходит, так это то, что ваш класс имеет неявное преобразование в тип указателя void* в этом случае. Вы возвращаете 0, который является макросом NULL, который принимается как тип указателя.

Указатели имеют неявное преобразование в booleans, а нулевые указатели преобразуются в false.

Действительно, у вас может быть другое неявное преобразование в указатель для Foo:

operator int*() const
{
    std::cout << "operator int* const" << std::endl;
    return new int(3);
}

И ваш выход изменится на

оператор int * const
True

Однако, если у вас оба, вы получите ошибку компилятора:

class Foo{
public:

    operator int*() const
    {
        std::cout << "operator int* const" << std::endl;
        return new int(3);
    }
    operator void*() const
    {
        std::cout << "operator void*() const" << std::endl;
        return 0;
    }
};

main.cpp: 26: 9: ошибка: преобразование из 'Foo' в 'bool' неоднозначно

Если, однако, вы явно определяете преобразование, тоже bool, то оно не является двусмысленным:

operator void*() const
{
    std::cout << "operator void*() const" << std::endl;
    return 0;
}

operator bool() const
{
     std::cout << "operator bool() const" << std::endl;
    return true;
} // <--- compiler chooses this one

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

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

Например, operator bool всегда будет выбран, но если вам пришлось выбирать из operator int и operator void*, тогда будет выбрано operator int, потому что он выбирает числовое преобразование по конверсиям указателей.

Однако, если у вас были как operator int, так и operator char, вы получили бы ошибку, потому что это как числовые интегральные преобразования.

Ответ 4

Любое интегральное преобразование operator будет работать одинаково. Вы возвращаете 0 в свой оператор, поэтому False.

operator [int,double,uint64_t,<any_integral>]() const
{
    std::cout << "integral operator called" << std::endl;
    return 0;
}

В логическом выражении может использоваться любой интегральный тип.

Ответ 5

Это может быть любой тип, который может использоваться в булевом контексте, а void * здесь не является особенным, eh?