Надежный метод получения первого параметра шаблона - PullRequest
0 голосов
/ 10 сентября 2018

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

template<typename T>
struct front {};

template<template<typename...> typename C, typename FirstT, typename... Args>
struct front<C<FirstT, Args...>> {using type = FirstT;};

template<typename T>
using front_t = typename front<T>::type;

template<typename...> struct foo{};

using std::is_same_v;
static_assert(is_same_v<front_t<foo<int, double>>, int>); // Ok
static_assert(is_same_v<front_t<foo<int, double>>, double>); // Fail (as expected)

Это, однако, не работает с шаблонами, которые имеют параметры-значения:

using std::array;
static_assert(is_same_v<front_t<array<int, 5>>, int>);
// error: no type named 'type' in 'struct front<std::array<int, 5> >'

Хорошо, теперь мне нужно учесть и любые значения параметров:

template<typename T>
struct front {};

// First parameter is a type, other parameters are types
template<template<typename...> typename C, typename FirstT, typename... Args>
struct front<C<FirstT, Args...>> {using type = FirstT;};

// First parameter is a type, other parameters are values
template<template<typename, auto...> typename C, typename FirstT, auto... Args>
struct front<C<FirstT, Args...>> {using type = FirstT;};

// First parameter is a value, other parameters are types
template<template<auto, typename...> typename C, auto FirstA, typename... Args>
struct front<C<FirstA, Args...>> {constexpr static const auto value = FirstA;};

// First parameter is a value, other parameters are values
template<template<auto...> typename C, auto FirstA, auto... Args>
struct front<C<FirstA, Args...>> {constexpr static const auto value = FirstA;};

// Avoid ambiguity if there's only a single type parameter
template<template<typename...> typename C, typename FirstT>
struct front<C<FirstT>> {using type = FirstT;};

// Avoid ambiguity if there's only a single value parameter
template<template<auto...> typename C, auto FirstA>
struct front<C<FirstA>> {constexpr static const auto value = FirstA;};

template<typename T>
using front_t = typename front<T>::type;

template<typename T>
const auto front_v = front<T>::value;

template<typename...> struct foo{};
template<auto...> struct bar{};

static_assert(std::is_same_v<front_t<foo<int>>, int>); // Ok
static_assert(std::is_same_v<front_t<foo<int, double>>, double>); // Fail (as expected)
static_assert(std::is_same_v<front_t<std::array<int, 5>>, int>); // Ok
static_assert(front_v<bar<5, 4>> == 5); // Ok
static_assert(front_v<bar<5, 4>> == 4); // Fail (as expected)

Но затем я попробую еще кое-что:

template<typename, typename, auto...> struct baz{};

static_assert(std::is_same_v<front_t<baz<int, int>>, int>);
// error: no type named 'type' in 'struct front<baz<int, int> >'

Это явно выходит из-под контроля.Теперь мне нужно не только беспокоиться о смешивании типов со значениями параметров, но и о порядке упорядочения этих параметров и писать специализацию для каждой их комбинации.Но все, что я хочу, это первый из этих параметров!Другие не должны иметь никакого значения.

Наконец, вопрос: могу ли я вообще "игнорировать" любые параметры шаблона, которые мне не нужны для этой черты типа?Под «обобщенно» я подразумеваю игнорирование как значений, так и типов.

Ответы [ 3 ]

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

Нет способа справиться с каждым делом.

Что можно сделать в качестве обходного пути:

  • предоставить typedef внутри класса

    template <typename T /*, */> class Foo {
        using first_type = T;
    }
    
  • использовать только тип и, возможно, std::integral_constant

    Bar<int, std::true_type, std::integral_constant<int, 42>> bar;
    
  • Обрабатывать только общие случаи, как array.

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

Ответ Якка точный (как всегда), но реализация container_first_type кажется чрезмерно сложной.Ниже приводится более простая версия:

template<typename, typename = std::void_t<>>
struct optional_key_type_impl {using type=void;};

template<typename T>
struct optional_key_type_impl<T, std::void_t<typename T::key_type>> {using type=typename T::key_type;};

template<typename T>
using optional_key_type = typename optional_key_type_impl<T>::type;

// This could be variadic and support multiple types, but there's really no need.
template<typename T, typename> struct select_impl{ using type=T; };
template<typename T> struct select_impl<void, T> { using type=T; };
template<typename T1, typename T2> using select_t = typename select_impl<T1, T2>::type;

template<typename T>
using container_first_type = select_t<optional_key_type<T>, typename T::value_type>;
0 голосов
/ 10 сентября 2018

Нет.

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

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

template<class T>
using value_type = typename T::value_type;

, который ловит кучуслучаев.Если мы хотим получить тип ключа для вспомогательных контейнеров, мы делаем:

template<class T>
using key_type = typename T::key_type;

namespace details {
  template<class...>using void_t=void;
  template<template<class...>class Z, class, class...Ts>
  struct can_apply:std::false_type {};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z,void_t<Z<Ts...>>, Ts...>:std::true_type {};
}
template<template<class...>class Z, class...Ts>
using can_apply = details::can_apply<Z,void,Ts...>;


template<template<class>class Z>
struct ztemplate1 {
  template<class T>
  using result = Z<T>;
};

template<bool b, template<class>class True, template<class> class False, class T>
using conditional_apply1 = typename std::conditional_t<
  b,
  ztemplate1<True>,
  ztemplate1<False>
>::template result<T>;

template<class X>
using container_first_type = conditional_apply1<
  can_apply<key_type, X>{},
  key_type,
  value_type,
  X
>;

и теперь container_first_type<std::map<std::string, int>> равен std::string, тогда как container_first_type<std::vector<int>> равен int, а container_first_type<std::array<double, 7>> равен double.

Живой пример

...