Как работают операторы преобразования на С++?

Рассмотрим этот простой пример:

template <class Type>
class smartref {
public:
    smartref() : data(new Type) { }
    operator Type&(){ return *data; }
private:
    Type* data;
};

class person {
public:
    void think() { std::cout << "I am thinking"; }
};

int main() {
    smartref<person> p;
    p.think(); // why does not the compiler try substituting Type&?
}

Как работают операторы преобразования на С++? (i.e), когда компилятор пытается подставить тип, определенный после оператора преобразования?

Ответ 1

Некоторые случайные ситуации, в которых используются и не используются функции преобразования.

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

Преобразование при прохождении аргумента

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

struct B { };
struct A {
  operator B() { return B(); }
};
void f(B);
int main() { f(A()); } // called!

Прохождение аргумента - это всего лишь один контекст инициализации копии. Другая - это "чистая" форма с использованием синтаксиса инициализации копирования

B b = A(); // called!

Преобразование в ссылку

В условном операторе возможно преобразование в ссылочный тип, если тип, преобразованный в значение, является значением l.

struct B { };
struct A {
  operator B&() { static B b; return b; }
};

int main() { B b; 0 ? b : A(); } // called!

Другое обращение к ссылке - это привязка ссылки непосредственно

struct B { };
struct A { 
  operator B&() { static B b; return b; }
};

B &b = A(); // called!

Преобразование указателей на функции

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

typedef void (*fPtr)(int);

void foo(int a);
struct test {
  operator fPtr() { return foo; }
};

int main() {
  test t; t(10); // called!
}

Эта вещь может иногда стать весьма полезной.

Преобразование в типы неклассов

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

struct test {
  operator bool() { return true; }
};

int main() {
  test t;
  if(t) { ... }
}

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

struct test {
  void operator[](unsigned int) { }
  operator char *() { static char c; return &c; }
};

int main() {
  test t; t[0]; // ambiguous
}

// (t).operator[] (unsigned int) : member
// operator[](T *, std::ptrdiff_t) : built-in

Вызов может быть неоднозначным, потому что для члена второй параметр нуждается в преобразовании, а для встроенного оператора сначала требуется преобразование, определяемое пользователем. Два других параметра отлично соответствуют друг другу. В некоторых случаях вызов может быть не двусмысленным (ptrdiff_t должен отличаться от int).

Шаблон функции преобразования

Шаблоны допускают некоторые приятные вещи, но лучше быть очень осторожными в них. Ниже приведен тип, конвертируемый в любой тип указателя (указатели элементов не рассматриваются как "типы указателей" ).

struct test {
  template<typename T>
  operator T*() { return 0; }
};

void *pv = test();
bool *pb = test();

Ответ 2

"." оператор не перегружен в С++. И всякий раз, когда вы говорите x.y, никакое преобразование автоматически не будет выполняться на x.

Ответ 3

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

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

Ответ 4

Конверсии не являются волшебными. Просто потому, что A имеет преобразование в B и B, метод foo не означает, что a.foo() вызовет B:: foo().

Компилятор пытается использовать преобразование в четырех ситуациях

  • Вы явно передали переменную другому типу
  • Вы передаете переменную в качестве аргумента функции, ожидающей другого типа в этой позиции (операторы считаются здесь функциями)
  • Вы назначаете переменную переменной другого типа
  • Вы используете переменную copy-construct или инициализируете переменную другого типа

Существует три типа преобразований, отличных от тех, которые связаны с наследованием

  • Встроенные преобразования (например, int-to-double)
  • Неявная конструкция, где класс B определяет конструктор, принимающий один аргумент типа A, и не помещает его с помощью "явного" ключевого слова
  • Пользовательские операторы преобразования, где класс A определяет оператор B (как в вашем примере)

Как компилятор решает, какой тип преобразования использовать и когда (особенно когда есть несколько вариантов), довольно сложно, и я бы сделал плохую работу, пытаясь сконденсировать его в ответ на SO. В разделе 12.3 в стандарте С++ обсуждается неявное построение и пользовательские операторы преобразования.

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

Ответ 5

Вы должны сделать

((person)p).think();

У компилятора нет информации для автоматического кастинга для человека, поэтому вам нужно явно отличать.

Если вы используете что-то вроде

person pers = p;

Тогда компилятор имеет информацию для неявного литья для человека.

Вы можете иметь "кастинг" через конструкторы:

class A
{
public:
   A( int );
};


A a = 10; // Looks like a cast from int to A

Вот несколько кратких примеров. Кастинг (неявный, явный и т.д.) Требует больше объяснений. Вы можете найти подробную информацию в серьезных книгах на С++ (см. Вопросы о книгах С++ о переполнении стека для хороших названий, например этот).

Ответ 6

При попытке использовать объект (ссылку) типа T, где требуется U, компилятор попытается выполнить одно (!) пользовательское действие (неявный оператор ctor или cast).

Оператор ., однако, всегда будет пытаться получить доступ к элементу объекта (ссылки) с левой стороны. Это точно так, как оно определено. Если вы хотите что-то более фантастическое, то что operator->() может быть перегружено.

Ответ 7

//Виртуальная таблица Fuction (VFT)

#include <iostream>

using namespace std;

class smartref {
public:
virtual char think() { }//for Late bindig make virtual function if not make virtual function of char think() {} then become early binding and pointer call this class function 
    smartref() : data(new char) { }
  operator char(){ return *data; }
private:
    char* data;
};

class person:public smartref
{
public:
    char think() { std::cout << "I am thinking"; }
};

int main() {
    smartref *p;//make pointer of class
    person o1;//make object of class
    p=&o1;//store object address in pointer
    p->think(); // Late Binding in class person
return 0;
}