Извлечь параметры класса шаблона по умолчанию

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

Я знаю, как извлечь параметры экземпляра класса шаблона, например:

// Just an example class for the demonstration
template<class A, class B=void>
struct example {};

// Template parameters storage class
template<class...An>
struct args;

// MPL utility that extracts the template arguments from a template class
template<class C>
struct get_args
{
    typedef args<> type;
};
template<template<class...> class C, class...An>
struct get_args< C<An...> >
{
    typedef args<An...> type;
};

// And the assertion
static_assert(
    std::is_same
    <    args<int,void>,
         get_args< example<int> >::type
    >::value,
    "Check this out"
);

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

// MPL utility that extract template default arguments from a template class
template<template<class...> class C>
struct get_default_args
{
    typedef /* what goes here? */ type;
};

// And the assertion
static_assert(
    std::is_same
    <    args<void>,
         get_default_args< example >::type
    >::value,
    "Check this out"
);

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

namespace detail {
    template< template<class> class >
    boost::mpl::size_t<1> deduce_nb_args();
    template< template<class,class> class >
    boost::mpl::size_t<2> deduce_nb_args();
    template< template<class,class,class> class >
    boost::mpl::size_t<3> deduce_nb_args();
    /* ... and so on ... */
}

// MPL utility that extract the number of template arguments of a template class
template<template<class...> class C>
struct get_nb_args :
  decltype(detail::deduce_nb_args<C>()) {};

// And the assertion
static_assert(
    get_nb_args< example >::value == 2,
    "Check this out"
);

Редактировать

Кажется, что в конце и снова MSVC мешает мне выполнить эту операцию. Что-то вроде следующего вызывает сбой компилятора с фатальной ошибкой C1001: Произошла внутренняя ошибка в компиляторе.

template<template<class...> class D> static
boost::boost::mpl::int_<0> mandatory(D<>*)
{ return boost::boost::mpl::int_<0>(); }
template<template<class...> class D> static
boost::mpl::int_<1> mandatory(D<void>*)
{ return boost::mpl::int_<0>(); }
template<template<class...> class D> static
boost::mpl::int_<2> mandatory(D<void,void>*)
{ return boost::mpl::int_<0>(); }
template<template<typename...> class D> static
boost::mpl::int_<-1> mandatory(...)
{ return boost::mpl::int_<-1>(); }

int check()
{
  return mandatory<example>(nullptr);
}

Следующая попытка приводит к ошибке C2976: "D": слишком мало аргументов шаблона

template<template<class,class> class D> static
boost::mpl::int_<0> mandatory2(D<>*)
{ return boost::mpl::int_<0>(); }
template<template<class,class> class D> static
boost::mpl::int_<1> mandatory2(D<void>*)
{ return boost::mpl::int_<0>(); }
template<template<class,class> class D> static
boost::mpl::int_<2> mandatory2(D<void,void>*)
{ return boost::mpl::int_<0>(); }

int check2()
{
  return mandatory2<example>(nullptr);
}

Поэтому мне кажется, что независимо от подхода MSVC запрещает программную реализацию класса шаблона с использованием параметров по умолчанию. В свою очередь, мне кажется невозможным использовать технику SFINAE для извлечения: 1. обязательного количества параметров; 2. типы параметров по умолчанию.

Редактировать 2

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

Я отправил отчет об ошибке сюда и еще один здесь.

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

namespace detail {
    typedef std::true_type true_;
    typedef std::false_type false_;

    template< template<class...> class D, class...An >
    true_ instantiable_test(D<An...>*);
    template< template<class...> class D, class...An >
    false_ instantiable_test(...);
}
template< template<class...> class C, class...An >
struct is_instantiable : decltype(detail::instantiable_test<C,An...>(nullptr)) {};

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

template< template<class...> class T, class...An >
struct get_default_v0
{
    typedef T<An...> type;
};

namespace detail {
    template< template<class...> class T, class...An >
    T<An...> try_instantiate();
} // namespace detail

template< template<class...> class T, class...An >
struct get_default_v1
{
    typedef decltype(detail::try_instantiate<T,An...>()) type;
};

// C2976
static_assert(
  std::is_same< get_default_v0<example,int> , example<int,void> >::value,
  "v0"
);

// C2976
static_assert(
  std::is_same< get_default_v1<example,int> , example<int,void> >::value,
  "v1"
);

person S. Paris    schedule 04.03.2015    source источник
comment
Что ж, вы можете подделать экземпляр с n voids впереди, каждый раз пытаясь сбой SFINAE, и если он создает экземпляр, передать его экстрактору аргументов, который извлекает типы после первого n? Опасения: если это не произойдет достаточно рано, SFINAE выйдет из строя. Но это может сработать. ... или то, что @KerrekSB предложил ниже   -  person Yakk - Adam Nevraumont    schedule 04.03.2015
comment
Также обратите внимание, что параметры шаблона по умолчанию могут зависеть от параметров, отличных от параметров по умолчанию (см., Например, std::vector).   -  person Tom Knapen    schedule 04.03.2015
comment
Я пробовал что-то подобное, но MSVC вылетает при попытке разрешить sfinae ... Возможно, я делаю это неправильно, но MSVC ужасно затрудняет отладку :(   -  person S. Paris    schedule 05.03.2015
comment
В конце концов, все зависит от того, сможет ли компилятор программно создать экземпляр типа шаблона, используя аргументы по умолчанию. Кажется, что он не поддерживается MSVC (еще раз), но поддерживается GCC и CLang (см. здесь).   -  person S. Paris    schedule 05.03.2015


Ответы (2)


Я бы попробовал что-то вроде этого:

template <typename ...> struct Get2;

template <template <typename...> class Tmpl,
          typename A, typename B, typename ...Rest>
struct Get2<Tmpl<A, B, Rest...>>
{
    using type = B;
};

template <template <typename...> class Tmpl> struct GetDefault2
{
    using type = typename Get2<Tmpl<void>>::type;
};
person Kerrek SB    schedule 04.03.2015
comment
Требуется оборудование, такое как n-void factory для набора типов, для которого тесты SFINAE могут создать экземпляр с пакетом, а затем, может быть, экстрактор хвоста пакета из шаблона? Но ничего сложного. - person Yakk - Adam Nevraumont; 04.03.2015
comment
В MSVC использование GetDefault2< example > приводит к ошибке C2976: "пример": слишком мало аргументов шаблона - person S. Paris; 05.03.2015

Я понимаю, что это длинный ответ, но вот возможный подход:

#include <type_traits>

namespace tmpl
{

namespace detail
{

template<template<typename...> class C, typename... T>
struct is_valid_specialization_impl
{
  template<template<typename...> class D>
  static std::true_type test(D<T...>*);
  template<template<typename...> class D>
  static std::false_type test(...);

  using type = decltype(test<C>(0));
};

} // namespace detail

template<template<typename...> class C, typename... T>
using is_valid_specialization = typename detail::is_valid_specialization_impl<C, T...>::type;

} // namespace tmpl

Ниже приведена частичная копия / вставка из моего репозитория github, не беспокойтесь об этом слишком сильно, большая часть кода предназначена для поиска минимального / максимального количества требуемых аргументов шаблона (в данном случае нас интересует только минимальное количество):

#if !defined(TEMPLATE_ARGS_MAX_RECURSION)
  #define TEMPLATE_ARGS_MAX_RECURSION 30
#endif

namespace tmpl
{

namespace detail
{

enum class specialization_state {
  invalid,
  valid,
  invalid_again
};

template<bool, template<typename...> class C, typename... T>
struct num_arguments_min
: std::integral_constant<int, sizeof...(T)>
{ };

template<template<typename...> class C, typename... T>
struct num_arguments_min<false, C, T...>
: num_arguments_min<is_valid_specialization<C, T..., char>::value, C, T..., char>
{ };

template<specialization_state, template<typename...> class C, typename... T>
struct num_arguments_max;

template<template<typename...> class C, typename... T>
struct num_arguments_max<specialization_state::invalid, C, T...>
: num_arguments_max<
  is_valid_specialization<C, T..., char>::value
    ? specialization_state::valid
    : specialization_state::invalid,
  C,
  T..., char
>
{ };

template<template<typename...> class C, typename... T>
struct num_arguments_max<specialization_state::valid, C, T...>
: std::conditional<
  ((sizeof...(T) == 0) || (sizeof...(T) == TEMPLATE_ARGS_MAX_RECURSION)),
  std::integral_constant<int, -1>,
  num_arguments_max<
    is_valid_specialization<C, T..., char>::value
      ? specialization_state::valid
      : specialization_state::invalid_again,
    C,
    T..., char
  >
>::type
{ };

template<template<typename...> class C, typename... T>
struct num_arguments_max<specialization_state::invalid_again, C, T...>
: std::integral_constant<int, (sizeof...(T) - 1)>
{ };

} // namespace detail

template<template<typename...> class C>
struct template_traits
{
  constexpr static int args_min = detail::num_arguments_min<is_valid_specialization<C>::value, C>::value;
  constexpr static int args_max = detail::num_arguments_max<is_valid_specialization<C>::value
                                                              ? detail::specialization_state::valid
                                                              : detail::specialization_state::invalid,
                                                            C>::value;

  constexpr static bool is_variadic = (args_max < args_min);

  template<typename... T>
  using specializable_with = is_valid_specialization<C, T...>;
};

} // namespace tmpl

Некоторые типы помощников специально для вашего вопроса:

template<typename... Ts>
struct type_sequence { };

namespace detail
{

template<int N, typename...>
struct skip_n_types;

template<int N, typename H, typename... Tail>
struct skip_n_types<N, H, Tail...>
: skip_n_types<(N - 1), Tail...> { };

template<typename H, typename... Tail>
struct skip_n_types<0, H, Tail...>
{
  using type = type_sequence<H, Tail...>;
};

} // namespace detail

template<int N, typename... T>
using skip_n_types = typename detail::skip_n_types<N, T...>::type;

namespace detail
{

template<typename T>
struct get_default_args;

template<template<typename...> class T, typename... A>
struct get_default_args<T<A...> >
{
  using type = typename skip_n_types<
                          tmpl::template_traits<T>::args_min,
                          A...>::type;
};

} // namespace detail

template<typename T>
using get_default_args = typename detail::get_default_args<T>::type;

Чтобы собрать все вместе:

template<typename>
struct dependant { };

template<typename T, typename U = void>
struct example { };

template<typename T, typename U = dependant<T> >
struct dependant_example { };

template<typename T>
void print_type(T)
{
  std::cout << __PRETTY_FUNCTION__ << std::endl;
}

int main(int argc, char** argv)
{
  {
    using example_type = example<int>;
    using default_args = get_default_args<example_type>;

    print_type(example_type{});
    print_type(default_args{});
  }
  {
    using example_type = dependant_example<int>;
    using default_args = get_default_args<example_type>;

    print_type(example_type{});
    print_type(default_args{});
  }
}

Выход:

void print_type(T) [T = example<int, void>]
void print_type(T) [T = type_sequence<void>]
void print_type(T) [T = dependant_example<int, dependant<int> >]
void print_type(T) [T = type_sequence<dependant<int> >]
person Tom Knapen    schedule 04.03.2015
comment
Спасибо за ответ Том, я пробовал нечто очень похожее, но, к сожалению, обновление MSVC 2013 update 4 (последняя версия) дает сбой в SFINAE для is_valid_specialization. Вы получаете только C1001: в компиляторе произошла внутренняя ошибка! Я пытался повернуть его разными способами, используя отложенный вспомогательный класс и т. Д., Но безуспешно. - person S. Paris; 05.03.2015