1

Is there a way to extract a template class' default parameters only knowing the unspecialized template class at compile time?

I know how to extract an instantiated template class' parameters, like this:

// 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"
);

Now what I would like to know is if decltype or anything else could be used to retrieve the default template parameters only from the unspecialized template class:

// 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"
);

For now, I only figured out how to extract the number of parameters of a template class, not their default value:

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"
);

Edit

It seems that at the end, and once again, MSVC prevents me to perform this operation. Something like the following makes the compiler crash with a fatal error C1001: An internal error has occurred in the compiler.

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);
}

Trying next one results in error C2976: 'D' : too few template arguments

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);
}

So to me it seems that no matter the approach, MSVC forbids programmatic instantiation of a template class making use of default parameters. In turn, it looks impossible to me to use a SFINAE technique to extract: 1. the mandatory number of parameters; 2. the types of default parameters.

Edit 2

Ok, after several tests it seems to be a bug with MSVC occurring when trying to programmatically instantiate a template class only using default arguments.

I filed a bug report here and another one here.

Here is a traits class allowing to check if a class is instantiable using given template parameters that does not make the compiler crash, but do not evaluates to true for fully default instantiable classes.

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)) {};

That being said, it seems impossible with MSVC to retrieve the template type instantiated with default parameters. Typically the following does not compile:

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"
);
S. Paris
  • 159
  • 4
  • 5
  • 1
    Well, you could fake instantiate with n `void`s at the front, each time trying to SFINAE fail, and if it instantiates pass it to an argument extractor that extracts types after the first n? Concerns: if it isn't an early enough failure, SFINAE would fail. But it might work. ... or what @KerrekSB suggested below – Yakk - Adam Nevraumont Mar 04 '15 at 16:04
  • Also note that template default parameters can depend on non-default parameters (see for example `std::vector`). – Tom Knapen Mar 04 '15 at 17:21
  • I tried something like that, but MSVC crashes when trying to resolve the sfinae... Perhaps I am not doing it right, but MSVC makes it terribly hard to debug :( – S. Paris Mar 05 '15 at 17:28
  • After all, everything depends on the compiler being able to instantiate a template type using default arguments programmatically. It seems that it is not supported by MSVC (once again) but is by GCC and CLang ([see here](http://stackoverflow.com/questions/24017466/is-it-required-to-explicitly-list-default-parameters-when-using-template-templat)). – S. Paris Mar 05 '15 at 18:31

2 Answers2

1

I'd try something like this:

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;
};
Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • Needs machinery, like an n-void factory of a pack of types, SFINAE tests for "can instantiate with a pack", and then maybe a pack-tail extractor from a template? But nothing hard. – Yakk - Adam Nevraumont Mar 04 '15 at 16:07
  • Under MSVC, using `GetDefault2< example >` results in _error C2976: 'example' : too few template arguments_ – S. Paris Mar 05 '15 at 17:45
1

I realize this is a long answer, but here is a possible approach:

#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

The following is a partial copy/paste from my github repository, dont worry too much about it, most of the code is to find the minimum/maximum number of template arguments required (in this case we only care about the minimum number):

#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

Some helper types specifically for your question:

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;

To put it all together:

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{});
  }
}

Output:

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> >]
Tom Knapen
  • 2,277
  • 16
  • 31
  • Thank you for the answer Tom, I tried something very similar, but unfortunately MSVC 2013 update 4 (last version) crashes on the SFINAE for `is_valid_specialization`. You only get a C1001: An internal error occured in the compiler! I tried to turn it in many different ways, using a delayed helper class etc. with no success. – S. Paris Mar 05 '15 at 17:34