【问题标题】:Extract template class default parameters提取模板类默认参数
【发布时间】:2015-03-04 15:54:45
【问题描述】:

有没有办法在编译时只知道非特化的模板类来提取模板类的默认参数?

我知道如何提取实例化模板类的参数,如下所示:

// 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' : too little 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);
}

所以在我看来,无论采用何种方法,MSVC 都禁止使用默认参数对模板类进行编程实例化。 反过来,在我看来,使用 SFINAE 技术来提取是不可能的: 1. 强制参数个数; 2. 默认参数的类型。

编辑 2

好的,经过多次测试,当尝试仅使用默认参数以编程方式实例化模板类时,似乎是 MSVC 的一个错误。

我提交了一个错误报告 here 和另一个 here

这是一个特征类,允许使用给定的模板参数检查一个类是否可实例化,不会导致编译器崩溃,但对于完全默认的可实例化类,不会评估为真

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

【问题讨论】:

  • 好吧,你可以在前面用 n voids 伪造实例化,每次尝试 SFINAE 失败时,如果它实例化将它传递给一个参数提取器,该提取器在第一个 n 之后提取类型?担忧:如果不是足够早的失败,SFINAE 就会失败。但它可能会奏效。 ...或@KerrekSB 下面的建议
  • 另请注意,模板默认参数可以依赖于非默认参数(参见例如std::vector)。
  • 我尝试了类似的方法,但是在尝试解决 sfinae 时 MSVC 崩溃了......也许我做得不对,但是 MSVC 让调试变得非常困难:(
  • 毕竟,一切都取决于编译器能够以编程方式使用默认参数实例化模板类型。似乎 MSVC(再次)不支持它,但 GCC 和 CLang (see here) 支持它。

标签: c++ templates c++11 template-meta-programming default-arguments


【解决方案1】:

我会尝试这样的:

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

【讨论】:

  • 需要机器,比如一组类型的 n-void 工厂,SFINAE 测试“可以用一个包实例化”,然后可能需要一个来自模板的包尾提取器?但没什么难的。
  • 在 MSVC 下,使用 GetDefault2&lt; example &gt; 会导致 error C2976: 'example' : too little template arguments
【解决方案2】:

我意识到这是一个很长的答案,但这是一种可能的方法:

#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 repository的部分复制/粘贴,不要太在意,大部分代码是找到所需的模板参数的最小/最大数量(本例我们只关心最小号):

#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> >]

【讨论】:

  • 感谢汤姆的回答,我尝试了一些非常相似的方法,但不幸的是,MSVC 2013 更新 4(最新版本)在 SFINAE 上为is_valid_specialization 崩溃。您只会得到 C1001:编译器发生内部错误!我尝试用许多不同的方式来改变它,使用延迟的帮助类等,但没有成功。
猜你喜欢
  • 2016-11-09
  • 1970-01-01
  • 1970-01-01
  • 2019-06-20
  • 1970-01-01
  • 2013-02-28
  • 1970-01-01
相关资源
最近更新 更多