Приоритеты операторов неявного преобразования С++

EDIT: после комментария Майка Сеймура я заменил operator std::string () const; на operator char * () const; и соответствующим образом изменил реализацию. Это позволяет использовать неявное кастинг, но по какой-либо причине оператор unsigned long int имеет приоритет над оператором char *, который просто не чувствует себя хорошо... Кроме того, я не хочу показывать неприятный материал C, например char * вне класса, когда у меня есть std::string. У меня есть догадка, что мой класс CustomizedInt должен наследовать от некоторых вещей, чтобы поддержать функцию, которую я желаю. Может ли кто-нибудь прокомментировать комментарий Майка относительно std::basic_string? Я не уверен, что правильно понял.


У меня есть эта часть кода:

#include <string>
#include <sstream>
#include <iostream>

class CustomizedInt
{
private:
    int data;
public:
    CustomizedInt() : data(123)
    {
    }
    operator unsigned long int () const;
    operator std::string () const;
};

CustomizedInt::operator unsigned long int () const
{
    std::cout << "Called operator unsigned long int; ";
    unsigned long int output;
    output = (unsigned long int)data;
    return output;
}

CustomizedInt::operator std::string () const
{
    std::cout << "Called operator std::string; ";
    std::stringstream ss;
    ss << this->data;
    return ss.str();
}

int main()
{
    CustomizedInt x;
    std::cout << x << std::endl;
    return 0;
}

Какие печатает "Вызывается оператор unsigned long int; 123". Мои вопросы таковы:

  • После того, как я удалю оператор unsigned long int, зачем мне явно указывать x на std::string? Почему он не вызывает неявный оператор литья (std::string) напрямую?
  • Есть ли какая-либо документация, объясняющая, какие неявные броски разрешены и каков их порядок приоритета? Кажется, что если я добавлю оператор unsigned int в этот класс вместе с оператором unsigned long int, я получаю ошибку компилятора о неоднозначности для < оператор...
  • Кроме того, я знаю, что определение такого оператора может быть плохой практикой, но я не уверен, что полностью понимаю связанные с этим оговорки. Может кто-нибудь, пожалуйста, изложить их? Лучше ли было бы просто определять общедоступные методы ToUnsignedLongInt и ToString?

Ответ 1

После того, как я удалю оператор unsigned long int, зачем мне явно указывать x на std::string? Почему он не вызывает неявный оператор литья (std::string) напрямую?

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

Есть ли какая-либо документация, которая объясняет, какие неявные броски разрешены и каков их порядок приоритета?

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

Я не уверен, что полностью понимаю связанные с этим предостережения. Может кто-нибудь, пожалуйста, набросайте их?

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

Лучше ли было бы просто определять общедоступные методы ToUnsignedLongInt и ToString?

Это, вероятно, хорошая идея, чтобы избежать нежелательных преобразований. Вы можете исправить свою проблему, оставив их и используя их явно при необходимости:

std::cout << std::string(x) << std::endl;

В С++ 11 вы можете объявить их explicit, чтобы их можно было использовать только таким образом. На мой взгляд, это был бы лучший вариант, если можно; в противном случае я бы использовал явные функции преобразования, которые вы предлагаете.

Кстати, возвращаемый тип main() должен быть int, а не void.