Упрощение двухцепочечных преобразований - PullRequest
0 голосов
/ 13 мая 2018

Вопрос

Я хочу преобразования между классами A, B, C, D, E.

Преобразование должно происходить пошагово.

Например, преобразование из A в E включает преобразования A-> B-> C-> D-> E.

Преобразование из E в B включает преобразования E-> D-> C-> B

Я попытался упростить ответ по адресу:

Дважды преобразованные цепочки между классами

Но сейчас он не компилируется.

  • Почему это работало раньше и не работает сейчас?

  • Возможно ли это исправить?

Еще раз спасибо.


Тест (также на godbolt.org )

#include <type_traits>
#include <iostream>

template<typename Self>
struct chain_converter {
    template<typename Other, typename = std::enable_if_t<
            std::is_constructible_v<Self, Other>
    >>
    operator Other() {
        return static_cast<Self*>(this)->operator Other();
    }
};

struct B;
struct C;

struct A : chain_converter<A> { operator B(); };    
struct B : chain_converter<B> {
    operator A();    
    operator C();
};    
struct C : chain_converter<C> { operator B(); };

A::operator B() { return B(); }    
B::operator A() { return A(); }    
B::operator C() { return C(); }    
C::operator B() { return B(); }

int main() {
    std::cout << std::is_constructible_v<B, A> 
              << "\n";
    A a = C();
    C c = A();
    return 0;
}

Ошибка компилятора (из godbolt.org ):

<source>: In function 'int main()':

<source>:37:11: error: conversion from 'C' to non-scalar type 'A' requested

     A a = C();

           ^~~

<source>:38:11: error: conversion from 'A' to non-scalar type 'C' requested

     C c = A();

           ^~~

Compiler returned: 1

Предыдущий код, который работает (также на coliru ):

#include <type_traits>
#include <iostream>
template <typename T1, typename T2>
struct indirect_conversion {
    template <typename T, 
              typename = std::enable_if_t<
                  std::is_constructible_v<T, T2>
              >
    >
    operator T() {
        return static_cast<T1 *>(this)->operator T2();
    }
};

struct A {};

struct B : indirect_conversion<B, A> {
    operator A() {
        std::cout << "B -> A\n";
        return A();
    }
};
struct C : indirect_conversion<C, B> {
    operator B() {
        std::cout << "C -> B\n";
        return B();
    }
};

int main() {
    A a = C();
}

Результат:

C -> B
B -> A

Ответы [ 3 ]

0 голосов
/ 13 мая 2018
 error: conversion from 'C' to non-scalar type 'A' requested

Эта ошибка просто говорит вам, что C не конвертируется в A.

Он не конвертируем, потому что нет ни преобразования operator A() в C, ни конвертирующего конструктора A(const C&) в A.

Возможно, вы предполагали, что оператор преобразования будет унаследован от chain_converter<C>, как он был унаследован в рабочей версии, но на самом деле эта база не имеет такого оператора преобразования, поскольку подстановка аргумента шаблона std::enable_if_t не удалась. Замена не выполнена, потому что std::is_constructible_v<C, A> неверно Это неверно, потому что C нельзя построить с аргументом A.


Ваше «упрощение» рабочего кода не может работать. Второй аргумент шаблона chain_converter необходим.

0 голосов
/ 13 мая 2018

После добавления кода для точного определения преобразований в исходном коде я получаю следующее:

Результат

Template conversion
    is_constructible<1A, 1C>
    call non-template conversion: 1D->1C
    return a 1C as a 1A
Non-template conversion: D -> C
Template conversion
    is_constructible<1A, 1B>
    call non-template conversion: 1C->1B
    return a 1B as a 1A
Non-template conversion: C -> B
Non-template conversion: B -> A

Шаг 1 :

После ручного перемещения кода из indirect_conversion в struct D я получаю следующее:

struct D {
    template<typename T, typename = std::enable_if_t<
            std::is_constructible_v<T, C>
    >>
    operator T() {
        return static_cast<D*>(this)->operator C();
    }
....

Основная функция запрашивает неявное преобразование из D в A.

Предположим, C является конструктивным из A.

Поскольку C является конструктивным из A, включите оператор неявного преобразования из D в A.

Определить неявное преобразование из D в A как:

  • Явно преобразует из D в C на static_cast<D*>(this)->operator C();.
    • Это Non-template: D -> C в результате.
  • Запросить неявное преобразование из C в A
    • Он запрашивает неявное преобразование, потому что оператор return возвращает C, но тип возврата T равен A.

Остальные шаги аналогичны.

Объяснение предположения

Последний раздел объясняет 1-й шаг. Все шаги вместе объясняют первоначальное предположение, что C является конструктивным из A.

B является конструктивным из A. Это значит:

  • Оператор преобразования из C в A включен. Это означает, что C можно построить из A.
    • Это означает, что оператор преобразования из D в A включен. Это означает, что D является конструктивным из A.

Модифицированный код для тестирования :

#include <type_traits>
#include <iostream>
template <typename T1, typename T2>
struct indirect_conversion {
    template <typename T,
            typename = std::enable_if_t<
                    std::is_constructible_v<T, T2>
            >
    >
    operator T() {
        auto T_name = typeid(T).name();
        auto T1_name = typeid(T1).name();
        auto T2_name = typeid(T2).name();
        std::cout << "Template conversion" << "\n";
        std::cout << "    is_constructible<"
                  << T_name << ", "
                  << T2_name << ">\n";
        std::cout << "    call non-template conversion: "
                  << T1_name << "->" << T2_name << "\n";

        std::cout << "    return a " << T2_name << " as a " << T_name << "\n";
        return static_cast<T1 *>(this)->operator T2();
    }
};

struct A {};

struct B : indirect_conversion<B, A> {
    operator A() {
        std::cout << "Non-template conversion: B -> A\n";
        return A();
    }
};
struct C : indirect_conversion<C, B> {
    operator B() {
        std::cout << "Non-template conversion: C -> B\n";
        return B();
    }
};

struct D : indirect_conversion<D, C> {
    operator C() {
        std::cout << "Non-template conversion: D -> C\n";
        return C();
    }
};

int main() {
    A a = D();
}
0 голосов
/ 13 мая 2018

Неявное преобразование допускается только один раз при рассмотрении аргумента для конструктора или пользовательской функции преобразования. Неявные преобразования - Cppreference
Как и в предыдущем коде, причина C может преобразовываться в A, потому что структура C наследует цепочку преобразований, и я добавляю небольшое изменение, чтобы помочь понять:

template <typename T1, typename T2>
struct conversion_chain {
    template <typename T, typename = std::enable_if_t<
        std::is_constructible_v<T, T2>
        >>
        operator T() {
        cout << "cnoversion from " << typeid(T1).name() << " to " << typeid(T).name() << " via " << typeid(T2).name() << " as below." << endl;
        return static_cast<T1 *>(this)->operator T2();
    }
};

И я помню, что "Внутри объектной модели Cpp" сказано, что неявное преобразование, вызываемое автоматически компилятором, допускается только один раз, но явное преобразование, такое как "A.operator B ()", не имеет ограничений на время вызова, если оно может правильно скомпилироваться.

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