Неявное преобразование между двумя классами на основе целочисленного типа - PullRequest
0 голосов
/ 11 сентября 2018

У меня есть ситуация, когда у меня есть класс A, который предоставляет конструктор для целочисленного типа, и класс B, который предоставляет оператор неявного преобразования для того же целого типа. Однако если я вызову функцию, принимающую ссылку на класс A с экземпляром класса B, компиляция завершится неудачно. Я ожидал бы неявного преобразования класса B в тип, принятый конструктором класса A. Конечно, если я добавлю конструктор к A, принимающему класс B, все будет хорошо. Это поведение предназначено? Пожалуйста, проверьте пример ниже.

#include <iostream>

class B
{
public:
        B() = default;
        B(const std::uint8_t &val) : mVal(val) {}

        std::uint8_t get() const { return mVal; }

        operator std::uint8_t() const { return mVal; }

private:
        std::uint8_t mVal;
};

class A
{
public:
        A() = default;
        A(const std::uint8_t &val) : mVal(val) {}

        // No problem if this exists
        // A(const B &b) : mVal(b.get()) {}

        std::uint8_t get() const { return mVal; }

private:
        std::uint8_t mVal;
};

void func(const A &a)
{
        std::cout << static_cast<int>(a.get()) << std::endl;
}

int main(int, char*[])
{
        std::uint8_t val = 0xCE;

        A a(val);
        B b(val);

        func(val); // fine
        func(a); // fine
        func(b); // error
}

Ответы [ 3 ]

0 голосов
/ 11 сентября 2018

Вам разрешено только одно пользовательское преобразование при неявном создании объекта.Так как func требуется A, у вас должно быть пользовательское преобразование, чтобы превратить B в std::uint8_t, а затем другое пользовательское преобразование, чтобы превратить это std::uint8_t в A.Вам понадобится operator A в B или конструктор в A, который принимает B, если вы хотите, чтобы это происходило неявно.В противном случае вы можете просто явным образом привести, так что вам нужен только один неявный, такой как

func(static_cast<std::uint8_t>(b)); // force it to a uint8_t
// or
func({b}); // make b the direct initializer for an A which will implicitly cast
// or
func(A{b}); same as #2 above but explicitly sating it
0 голосов
/ 11 сентября 2018

В C ++ существует правило, согласно которому никакое неявное преобразование не будет использовать два пользовательских преобразования.

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

Если вы хотите иметь возможность конвертировать из всего, что может конвертировать в uint8_t, вы можете сделать:

template<class IntLike,
  std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
  std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true
>
A( IntLike&& intlike ):A( static_cast<std::uint8_t>(std::forward<IntLike>(intlike)) )
{}

, или вы можете разыграть B до uint8_t в точке, которую вы хотите преобразовать в A.

Вы можете сделать аналогичную вещь в B, где вы создаете магический template<class T, /*SFINAE magic*/> operator T, который превращается во все, что может быть построено с помощью uint8_t.

Этот неясный код:

  std::enable_if_t<std::is_convertible_v<IntLike, std::uint8_t>, bool> =true,
  std::enable_if_t<!std::is_same_v<A, std::decay_t<IntLike>>, bool> =true

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

В первом предложении enable_if говорится, что нам нужны только вещи, которые можно преобразовать в uint8_t. Во-вторых, мы не хотим, чтобы этот конструктор использовался для самого типа A, даже если он проходит первый.

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

Используемая техника называется SFINAE или Ошибка замещения не является ошибкой. Когда выводится тип IntType и эти тесты не выполняются, в этих предложениях возникает ошибка замещения. Обычно это вызывает ошибку, но при оценке перегрузок шаблона это не ошибка, потому что SFINAE; вместо этого он просто блокирует рассмотрение этого шаблона в разрешении перегрузки.

0 голосов
/ 11 сентября 2018

Это поведение предназначено?

Да, это предназначено.

Последовательность неявного преобразования может иметь не более одного пользовательского преобразования (конструктор или функция преобразования).

Стандарт говорит (выделено мое):

[over.ics.user]

Пользовательская последовательность преобразования состоит из начальной стандартной последовательности преобразования, за которой следует a user- определенное преобразование (15.3), за которым следует вторая стандартная последовательность преобразования. ...


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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...