Deduce / erase类型的模板模板参数

时间:2018-01-28 19:13:47

标签: c++ c++11 templates variadic-templates template-templates

使用模板模板参数时,如何推断或删除模板模板的模板类型?

考虑以下SSCCE:

#include <cstdint>
#include <cstddef>
#include <iostream>
using namespace std;

template<int i>
struct Value { };

template<int i>
struct BadValue { };

template<typename... G>
struct Print;

template<template<int> class ValueType, int... Is>
struct Print< ValueType<Is>... > {
    static void print() {
        const int is[] = { Is... };
        for (int i: is)
            cout << i;
        cout << endl;
    }

};

using V1 = Value<1>;
using V2 = Value<2>;
using V3 = Value<3>;
using BV = BadValue<1>;

int main() {
    Print<V2, V1, V2, V3>::print(); // <-- fine
    Print<V2, V1, V2, BV>::print(); // <-- BV used by accident
}

template<int> class ValueType类的Print参数推导到类似ValueBadValue类的模板类,强制参数包中的所有模板参数都为{ {1}}类是同一Print模板类的特化 - 这是故意的。也就是说,ValueType函数中的第二行会导致编译时错误,因为无法推导出main()参数以匹配ValueTypeValue类。如果用户在使用BadValue模板时意外尝试混合模板,则会出现编译时错误,这会提供一些诊断。

但是,上述实现仍然为Print模板模板参数的内部模板参数修复了int类型。如何删除它并将其推断出来?

一般来说,在推断模板模板参数时,如何访问内部模板参数?

2 个答案:

答案 0 :(得分:1)

如果我理解正确,您希望Print<V2, V1, V2, VB>::print();生成一个更容易理解的错误。

为此,我能想象的最好的方法是使用static_assert() s。

在这种特殊情况下 - Print是一个struct,只实现了部分专业化,没有实现通用版本 - 可用的不是一个非常简单的解决方案:实现通用版本给出一个static_assert()错误,显示您选择的消息。

以示例

template <typename ... G>
struct Print
 {
   static_assert( sizeof...(G) == 0, "not same int container for Print<>");

   static void print()
    { };
 };

template <template<int> class ValueType, int ... Is>
struct Print< ValueType<Is>... >
 {
   static void print()
    {
      using unused = int const [];

      (void)unused { (std::cout << Is, 0)... };

      std::cout << std::endl;
    }
 };

不幸的是,此解决方案接受为有效Print<>;我不知道你是否对你好。

另一个(更好的,恕我直言,但更精细)解决方案可以转换接受可变Print容器的专业化中的int部分特化(变量ValueTypes而不是固定{{1}并且,在ValueType中,检查(使用自定义类型特征)所有容器都是相同的。

再见示例,具有以下自定义类型特征

static_assert()

您可以按如下方式编写template <template <int> class ...> struct sameCnts : public std::false_type { }; template <template <int> class C0> struct sameCnts<C0> : public std::true_type { }; template <template <int> class C0, template <int> class ... Cs> struct sameCnts<C0, C0, Cs...> : public sameCnts<C0, Cs...> { }; 专业化

Print

如果你可以使用C ++ 17,你可以使用折叠,并且可以写出类型特征

template <template <int> class ... Cs, int ... Is>
struct Print< Cs<Is>... >
 {
   static_assert(sameCnts<Cs...>{}, "different containers in Print<>");

   static void print()
    {
      using unused = int const [];

      (void)unused { (std::cout << Is, 0)... };

      std::cout << std::endl;
    }
 };

和(在template <template <int> class, template <int> class> struct sameCnt : public std::false_type { }; template <template <int> class C> struct sameCnt<C, C> : public std::true_type { }; template <template <int> class C0, template <int> class ... Cs> struct sameCnts : public std::bool_constant<(sameCnt<C0, Cs>::value && ...)> { }; 方法中使用折叠)print()如下

Print

- 编辑 -

OP问

  

但是,如何让Print类接受,例如,专门用于double非类型值而不是int非类型值的类型?

不确定你想要什么但是(记住template <template <int> class ... Cs, int ... Is> struct Print< Cs<Is>... > { static_assert( sameCnts<Cs...>{}, "different containers in Print<>"); static void print() { (std::cout << ... << Is) << std::endl; } }; 值不能是模板非类型参数)我想你想要一个接受非类型的double -types模板参数,此非类型模板参数的类型未按示例(Print)修复。

对于C ++ 11和C ++ 14,我认为有必要明确非类型值的类型。

我的意思是......如果您按以下方式编写int

Print

你必须这样使用它

template <typename ...>
struct Print;

template <typename T, template <T> class ... Cs, T ... Is>
struct Print< T, Cs<Is>... >
 {
   static_assert(sameCnts<Cs...>{}, "different containers in Print<>");

   // ...
 };

表示Print<int, V2, V1, V2, V3>::print(); (或int或其他)作为第一个模板参数。这是因为无法推断long类型。

从C ++ 17开始,您可以使用int作为非类型模板参数的类型,因此您可以按如下方式编写auto

Print

并且无需明确类型,您可以编写

template <typename ...>
struct Print;

template <template <auto> class ... Cs, auto ... Is>
struct Print< Cs<Is>... >
 {
   static_assert( sameCnts<Cs...>{}, "different containers in Print<>");

   static void print()
    { (std::cout << ... << Is) << std::endl; }
 }; 

在这种情况下,您必须在Print<V2, V1, V2, V3>::print(); auto中使用int代替sameCnt

答案 1 :(得分:0)

如果您使用的是C ++ 17,则可以declare non-type template parameter with auto,因此只需将Is声明为auto...,然后使用auto代替int尽可能使用函数定义。

当然,由于Is的元素类型可能不同,因此可能无法声明数组is。相反,您可以改为使用std::tupleprint the tuple

// print_tuple is used to print a tuple

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp)>::type
  print_tuple(const std::tuple<Tp...>&)
  { }

template<std::size_t I = 0, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp)>::type
  print_tuple(const std::tuple<Tp...>& t)
  {
    std::cout << std::get<I>(t);  
    print_tuple<I + 1, Tp...>(t);
  }

// ...

template<template<int> class ValueType, auto... Is>
                                     // ^^^^
struct Print< ValueType<Is>... > {
    static void print() {
        print_tuple(std::make_tuple(Is...)); // make a tuple, and print it
    }
};

LIVE EXAMPLE

上面的模式(制作元组然后处理元组)允许您将一些复杂的函数应用于参数包Is。但是,如果您只想打印包,则可以选择使用C ++ 17功能fold expression,这样更简单。

template<template<int> class ValueType, auto... Is>
                                     // ^^^^
struct Print< ValueType<Is>... > {
    static void print() {
        (std::cout << ... << Is); // fold expression, also C++17 feature
    }
};

LIVE EXAMPLE