Обеспечение безопасности типов без вариантов - PullRequest
0 голосов
/ 15 января 2020

Рассмотрим следующие классы:

template <typename T>
class A {
 public:
  A(B<T> b) : b_(b) { }

  T foo() {
    return b_.foo();
  }
 private:
  class B<T> b_;
}

template typename<T>
class B {
 public:
  T foo();
}

Это позволяет принудительно печатать по стеку (вы можете продолжать добавлять больше слоев и печатать их на одном типе. Однако я хотел бы иметь две разные опции на второй слой:

template <typename T, typename Y>
class A {
 public:
  T foo() {
    return b_.foo();
  }
  Y foo() {
    return c_.foo();
  }

 private:
  class B<T> b;
  class C<Y> c;

}

template typename<T>
class B {
 public:
  T foo();
}

template typename<T>
class C {
 public:
  T foo();
}

Есть ли какой-нибудь способ, которым я мог бы храмить класс с несколькими типами имен и достичь этой схемы? Помните, в некоторых ситуациях T и Y могут быть одинаковыми, поэтому должно быть еще несколько различий (например, B<T> против C<Y>).

Я считаю, что у меня может быть несколько функций foo1 и foo2, возвращающих разные значения. Однако я ищу расширяемое решение, в котором клиенты могут предоставить свои собственные typenames и, возможно, больше двух. В идеале, должна быть одна перегрузка (может быть, идентификация внутреннего класса?)

1 Ответ

1 голос
/ 19 января 2020

Ваша идея сложна для понимания, но я думаю, что вы хотите взять список template<typename> typename s и равное число typename s и сконструировать продукт из их заархивированного приложения?

// wrap a template<typename> typename into a normal type
// e.g. index_of_v<std::vector, std::vector> fails, but
// index_of_v<suspend<std::vector>, suspend<std::vector>> gives 0
template<template<typename> typename>
struct suspend { };
// search for X in Xs and produce a static constexpr std::size_t member values containing the index
// This is just the natural functional programming way to search a linked list
// indexOf(x, Nil) = undefined
// indexOf(x, Cons(y, xs)) = x == y ? 0 : 1 + indexOf(x, xs)
// deriving from std::integral_constant is really just noise;
// could be replaced with an explicit member definition
// but it's considered idiomatic to do this for some reason that I've forgotten
template<typename X, typename... Xs>
struct index_of { }; // base case, the default template only fires when Xs is empty, so the index is undefined 
template<typename X, typename... Xs>
struct index_of<X, X, Xs...> : std::integral_constant<std::size_t, 0> { }; // if X is at the top of the list, it has index 0
template<typename X, typename Y, typename... Xs>
struct index_of<X, Y, Xs...> : std::integral_constant<std::size_t, index_of<X, Xs...>::value + 1> { }; // if it isn't, find it's index relative to the tail of the list, then shift it to be the index relative to the whole
// instead of writing index_of<X, Xs..>::value, write index_of_v<X, Xs...>
// this is a convention that you see in the standard library
template<typename X, typename... Xs>
inline constexpr std::size_t index_of_v = index_of<X, Xs...>::value;

// a class cannot have two lists of variadic template parameters
// the easiest thing to do is split the templating into two stages
// the outer template, choices, takes a list of templates as parameters
// template<typename T> class std::vector;
// int is a "base" type, so you can pass int to std::vector<int>
// but std::vector is not like int: std::vector<std::vector> is meaningless
// std::vector alone is just a template<typename> typename
// a function from types to types
template<template<typename> typename... Cs>
struct choices {
    // 2nd "stage" takes the list of "normal" types
    template<typename... Ts>
    class type {
        // apply each template to the corresponding base type
        // the resulting list of base types is passed to std::tuple
        // there's a "field" for each Cs<Ts> inside the tuple
        std::tuple<Cs<Ts>...> parts;

        public:
        // hopefully self-explanatory
        type(Cs<Ts>... parts) : parts(parts...) { }

        // return the result of calling foo() on the container identified by C
        template<template<typename> typename C>
        auto foo() {
            // we know the list of all the Cs,
            // so just find the index of C in it and pass that to std::get
            // then pass in parts to get the desired object, then call foo()
            return std::get<index_of_v<suspend<C>, suspend<Cs>...>>(parts).foo();
        }
    };

    // there's no luck for deducing Cs, since in order to *get* to types we need to specify Cs
    // (choices::type is not a thing, you always write choices<Cs...>::type)
    // But you can deduce `Ts`, by looking at the things contained in each container
    // so, deduction guide
    template<typename... Ts>
    type(Cs<Ts>...) -> type<Ts...>;
};

Ничего особенно интересного здесь не происходит. У вас есть список шаблонов контейнеров и список содержащихся типов, и каждый объект содержит кортеж объектов нужных типов. Что-то должно быть передано foo, чтобы он знал, какой объект получить; Самый разумный вариант - это тип контейнера. Вы также можете использовать содержащийся тип, но, очевидно, они не уникальны, так что это не сработает. Вы также можете просто передать индекс напрямую. Поскольку мы не знаем весь тип объекта, есть вспомогательная функция типа index_of, чтобы найти необходимый индекс для передачи в std::get. Для простоты index_of принимает только typename аргументов. Так как мы хотим найти template<typename> typename в списке таких, все они обернуты в suspend, чтобы заставить его работать.

Вы восстанавливаете два типа A, например:

template<typename T>
struct B {
    T x;
    B(T x) : x(x) { }
    T foo() { return x; }
};
using A1 = choices<B>;
void demonstration1() {
    A1::type a(B(5)); // note the deduction guide at work
    std::cout << "B: " << a.foo<B>() << "\n";
}

template<typename T>
struct C {
    T x;
    C(T x) : x(x) { }
    T foo() { return -x; } // keeping it interesting
};
using A2 = choices<B, C>;
void demonstration2() {
    A2::type a(B('a'), C(5));
    std::cout << "B: " << a.foo<B>() << "; C: " << a.foo<C>() << "\n";
}

Годболт

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