【问题标题】:Deduce/erase type of template template argument推断/擦除模板模板参数的类型
【发布时间】:2018-07-07 12:04:10
【问题描述】:

当使用 template template 参数时,我怎样才能推导出或删除 template template 的模板类型?

考虑以下 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
}

Print 类的template&lt;int&gt; class ValueType 参数推导出为像ValueBadValue 类这样的模板类强制Print 类的参数包中的所有模板参数都是相同的特化ValueType 模板类 - 这是故意的。也就是说,main() 函数中的第二行会导致编译时错误,因为无法推断出ValueType 参数以匹配ValueBadValue 类。如果用户在使用Print 模板时不小心尝试混合模板,则会出现编译时错误,这提供了一些诊断。

然而,上述实现仍然为ValueType 模板模板参数的内部模板参数固定了int 类型。我怎样才能擦除它并推导出它?

一般来说,当推导一个模板模板参数时,如何访问inner模板参数?

【问题讨论】:

  • 您的代码中似乎有一个错字:VB -> BV

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


【解决方案1】:

如果我理解正确,您希望Print&lt;V2, V1, V2, VB&gt;::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&lt;&gt;;不知道对你有没有好处。

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

举个例子,使用以下自定义类型特征

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 特化如下

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

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

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如下

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

-- 编辑--

OP 询问

但是我怎样才能让 Print 类也接受,例如,专门用于双非类型值而不是 int 非类型值的类型?

不确定你想要什么但是(记住double 值不能是模板非类型参数)我想你想要一个Print 接受具有非类型模板参数的类型,当此非类型模板参数的类型不像您的示例中那样固定 (int)。

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

我的意思是......如果你写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,或其他)解释为第一个模板参数。这是因为无法推断出int 类型。

从C++17开始你可以使用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();

在这种情况下,您还必须在sameCntsameCnts 中使用auto 而不是int

【讨论】:

  • 也许最好解释一下原始帖子中出了什么问题:如果没有可变参数模板模板参数,它会被推断为两个冲突的模板。
  • @TimSeguine - 我试图解释这一点,但我不善于言辞......我会尽力解释得更好。
  • 实际上,我完全希望它在模板冲突时出错。我想我的措辞也不是那么清楚 :-) 问题是我想推导出 int
  • @Kamajii - 嗯……是的,我现在真的很困惑;你想要一个错误但你想要推导出int 吗?建议:用例子解释你到底想要什么,但要修改问题(以便其他读者能注意到)。
  • 是的,完全正确。 Print 模板应强制其所有模板参数具有相同的(模板)类型。这就是为什么我不希望模板本身成为参数包的一部分。
【解决方案2】:

如果你在 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

【讨论】:

  • 所以嵌套template&lt;&gt;中给出的类型实际上并不重要?
  • @Kamajii 是的,这是故意的吗?我还更新了答案以添加更多详细信息。
猜你喜欢
  • 2023-03-31
  • 2015-02-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多