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

Вопрос

У меня есть серия из ~ 10 шаблонных классов A, B, C, D, ...

Я хочу включить преобразования из класса в предыдущие классы серии:

  • D -> C, B или A
  • C -> B или A
  • B -> A

Как это сделать без использования публичного наследования?


Тест 1 (публичное наследство)

  • Я не хочу наследования открытых методов.

Тест 2 (определить 1 + 2 + ... n операторов преобразования):

  • Определить n операторов преобразования для n-го класса шаблона шаблон класса.
  • Очень утомительно

Тест 3 (1 оператор преобразования на класс):

  • разрешить преобразование только из типа следующего уровня в тип этого уровня.

Например, это разрешает преобразование из D в C, но не из D в B.

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

template <typename Convertible>
class A {
public:
    operator Convertible() { return Convertible(); }
};

using B = A<int>;
using C = A<B>;
using D = A<C>;

int main() {
    D d;
    auto b = B(d);
    return 0;
}

Ошибка компиляции:

error: no matching function for call to ‘A<int>::A(D&)’
     auto b = B(d);
                 ^

Фактический вариант использования

A, B, C, D ... каждый узел (прокси), созданный слоем объекта S.

  • Слои типа 1 определяют организацию памяти узлов графа (Указатель / массив).

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

Пользователь может сложить слои разными способами, чтобы создать объект S.

Я хочу преобразовать узлы одного слоя в узлы предыдущих слоев.

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

Ответы [ 2 ]

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

Хотя подход @ hlt будет делать то, что вы просите, не зная больше о контексте, я настороженно отношусь к реализации преобразования в A. В тех случаях, о которых я могу думать, A не должен знать о B, C или D, поэтому я предложу другую реализацию.

Вы можете создать вариант теста 3, в котором вы реализуете один оператор преобразования на класс, , но , где вы также наследуете шаблонный оператор косвенного преобразования, например:

#include <type_traits>

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();
};
struct C : indirect_conversion<C, B> {
    operator B();
};
struct D : indirect_conversion<D, C> {
    operator C();
};
A a = D();
0 голосов
/ 12 мая 2018

Этого можно добиться, ограничив шаблонный конструктор (который будет использоваться при преобразовании) с помощью std::enable_if и некоторого метапрограммирования шаблона:

template <template <class> typename BaseTemplate,
          typename From,
          typename To,
          typename Enable = void>
struct may_convert
    : public std::false_type {};

template <template <class> typename BaseTemplate,
          typename T>
struct may_convert<BaseTemplate, BaseTemplate<T>, BaseTemplate<T>, void>
    : public std::true_type {};

template <template <class> typename BaseTemplate,
          typename T,
          typename U>
struct may_convert<BaseTemplate, BaseTemplate<T>, BaseTemplate<U>, 
                   typename std::enable_if<!std::is_same<T, U>::value>::type>
    : public may_convert<BaseTemplate, T, BaseTemplate<U>> {};

may_convert будет проходить вверх по шаблонам From параметр шаблона, пока он не станет равным To (в этом случае он наследует от std::true_type, т.е. may_convert<...>::value равен true), или пока не закончатся шаблоны (в этом случае may_convert<...>::value будет false).

Теперь все, что остается, - это соответственно ограничить ваш конструктор:

template <typename Convertible>
class A {
public:
    A() {}

    template <typename T,
              typename = typename std::enable_if<
                  may_convert<A, T, A<Convertible>>::value>::type>
    A(const T&) {}
};

Таким образом, конструктор существует, только если may_convert<...>::value равен true.В противном случае преобразование завершится неудачей.


Примеры

Вот пример того, как may_convert работает в вашем примере (преобразование из D = A<A<A<int>>> в B = A<int>):

  • Конструктор существует, только если may_convert<A, D, B>::value равно true

  • may_convert<A, D, B> соответствует последней специализации (потому что D = A<C> и B = A<int>параметры выводятся как T = C и U = int) и наследуются от may_convert<A, C, B>

  • may_convert<A, C, B>, снова совпадающих с последней специализацией (T = B, U = int) инаследуется от may_convert<A, B, B>

  • На этот раз оба типа совпадают, поэтому первая специализация совпадает, и все наследуется от std::true_type, что позволяет конструктору.

С другой стороны, представьте using E = A<double>, который не должен преобразовываться в B:

  • Конструктор будет включен, только если may_convert<A, E, B>::valuetrue

  • may_convert<A, E, B> соответствует последней специализации и наследуется от may_convert<A, double, B>

  • , поскольку double не является A<...>, ни одна из специализаций не совпадает, поэтому мы возвращаемся к регистру по умолчанию, который наследуется от std::false_type.

  • Следовательно, may_convert<A, E, B>::value равно false, и преобразованиене удается.

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