Неявное преобразование из пользовательского типа в примитивный тип в C++

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

Что если я хочу, чтобы мой класс неявно конвертировался в int?

Например, какую функцию нужно добавить внутри или вне SimpleClass, чтобы основная функция компилировала и выводила "1" на консоль? (см. комментарии)

#include <iostream>

class SimpleClass
{
private:
    int m_int;
public:
    SimpleClass(int value)
    : m_int(value) {}
};

int main(int argc, const char * argv[])
{
    SimpleClass simpleclass(1);
    int i = simpleclass; // does not complile
    std::cout << i << std::endl; // should output "1" to the console
    return 0;
}

3 ответа

Решение

Неявные преобразования могут быть определены двумя способами:

  • неявный конструктор с одним аргументом.
  • неявная функция преобразования (оператор преобразования), N3337 12.3.2

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

class SimpleClass {
   // ...
   operator int() const;
};

SimpleClass::operator int() const
{
   return m_int;
}

Технический.

Преобразование в (почти) любой тип T может быть выполнен operator T функция-член.

По умолчанию он вызывается неявно, и если вы объявите это const он также может быть вызван на const объект.

Таким образом:

struct MyType
{
    operator int() const { return 1; }
};

Проблемы…

Неявное преобразование в базовый тип позволяет свободно играть для всех встроенных операторов, включая

  • Арифметические операторы.
  • Булевы операторы.
  • Реляционные операторы.

Поэтому вам лучше убедиться, что все это работает так, как вы хотите.

И это может быть много работы!

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

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

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

Что вы можете с этим поделать.

Избегайте неявного преобразования, но предлагайте явное преобразование.

Лучшее общее явное преобразование - это, IMHO, именованная функция-член.

Альтернативой является operator T с префиксом с ключевым словом explicit, который поддерживается для этого использования в C++11 и более поздних версиях (в C++03 он может использоваться только на конструкторах).

Если вы хотите вывод через << вести себя так, как будто выполняется неявное преобразование, а затем просто определить operator<<,

И аналогично для других ситуаций, где неявное преобразование может оказаться общим решением: просто определите, что подходит для этой конкретной ситуации, и избегайте введения общего неявного преобразования.


Чтобы обеспечить неявное преобразование во встроенный тип и при этом избежать "бесплатного для всех" для встроенных операторов, вы можете использовать шаблонное преобразование типов, например, так:

#include <iostream>

template< class A, class B > struct Is_t_;

template< class Type > struct Is_t_<Type, Type> { using T = void; };

template< class A, class B >
using If_is_ = typename Is_t_<A, B>::T;

struct Bad_string
{
    operator const char* () const { return "666!"; }
    Bad_string( char const* = 0 ) {}
};

auto operator==( Bad_string const&, Bad_string const& )
    -> bool
{ return true; }

struct Good_string
{
    template< class Type, class Enabled_ = If_is_<const char*, Type>>
    operator Type() const { return "42 :)"; }

    Good_string( char const* = 0 ) {}
};

auto operator==( Good_string const&, Good_string const& )
    -> bool
{ return true; }

#if defined( DO_GOOD )
    using String = Good_string;
#elif defined( DO_BAD )
    using String = Bad_string;
#else
#   error "Define either DO_GOOD or DO_BAD, please."
#endif

auto main() -> int
{
    String a, b;
    (void) (a == "alfalfa");        // Errs for Bad_string
    (void) (a + 1);                 // Compiles for Bad_string.
}

Как ни странно, когда DO_GOOD определено, что в этом коде происходит сбой компилятора Visual C++ 2015 с обновлением 1, так называемого "ICE" (Внутренняя ошибка компилятора).

Обходной путь для этого компилятора состоит в том, чтобы вместо этого определить If_is_ как

template< class A, class B >
using If_is_ = std::enable_if_t< std::is_same<A, B>::value >;

Чтобы ваш класс был преобразован в int, воплощать в жизнь

operator int() const
{
  return m_int;
}
Другие вопросы по тегам