Deduce/erase 模板模板参数的类型
Deduce/erase type of template template argument
当使用 模板模板 参数时,如何推断或删除 模板模板 的模板类型?
考虑以下 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
class 的 template<int> class ValueType
参数推导为模板 class,如 Value
和 BadValue
classes 强制 Print
class 参数包中的所有模板参数都是同一 ValueType
模板 class 的特化 - 这是故意的。也就是说,main()
函数中的第二行导致编译时错误,因为无法推断 ValueType
参数同时匹配 Value
和 BadValue
class是的。如果用户在使用 Print
模板时不小心尝试混合模板,则会出现编译时错误,这会提供一些诊断信息。
然而,上面的实现仍然有 int
类型固定用于 ValueType
模板模板参数的内部模板参数。我怎样才能擦除它并推导它呢?
一般来说,在推导一个模板template参数时,如何访问inner模板参数?
如果我理解正确,您希望 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
而不是固定 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
特化
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就可以用folding,type traits可以写
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 && ...)>
{ };
and(也在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 问
But how can I have the Print class accept also, for example, types that are specialized for a double non-type value instead of the int non-type values?
不确定你想要什么但是(记住 double
值不能是模板非类型参数)我想你想要一个 Print
接受非类型的类型-types 模板参数,当此非类型模板参数的类型不像您的示例那样固定时 (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();
在这种情况下,您必须在 sameCnt
和 sameCnts
中使用 auto
而不是 int
。
如果你在 C++17 中工作,你可以 ,所以只需将 Is
声明为 auto...
,并使用 auto
而不是 int
] 尽可能在函数定义中。
当然,由于Is
的元素类型可能不同,所以可能无法声明数组is
。相反,您可以使用 std::tuple
和 print 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
}
};
上面的模式(创建元组然后处理元组)允许您将一些复杂的函数应用于参数包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
}
};
当使用 模板模板 参数时,如何推断或删除 模板模板 的模板类型?
考虑以下 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
class 的 template<int> class ValueType
参数推导为模板 class,如 Value
和 BadValue
classes 强制 Print
class 参数包中的所有模板参数都是同一 ValueType
模板 class 的特化 - 这是故意的。也就是说,main()
函数中的第二行导致编译时错误,因为无法推断 ValueType
参数同时匹配 Value
和 BadValue
class是的。如果用户在使用 Print
模板时不小心尝试混合模板,则会出现编译时错误,这会提供一些诊断信息。
然而,上面的实现仍然有 int
类型固定用于 ValueType
模板模板参数的内部模板参数。我怎样才能擦除它并推导它呢?
一般来说,在推导一个模板template参数时,如何访问inner模板参数?
如果我理解正确,您希望 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
而不是固定 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
特化
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就可以用folding,type traits可以写
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 && ...)>
{ };
and(也在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 问
But how can I have the Print class accept also, for example, types that are specialized for a double non-type value instead of the int non-type values?
不确定你想要什么但是(记住 double
值不能是模板非类型参数)我想你想要一个 Print
接受非类型的类型-types 模板参数,当此非类型模板参数的类型不像您的示例那样固定时 (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();
在这种情况下,您必须在 sameCnt
和 sameCnts
中使用 auto
而不是 int
。
如果你在 C++17 中工作,你可以 Is
声明为 auto...
,并使用 auto
而不是 int
] 尽可能在函数定义中。
当然,由于Is
的元素类型可能不同,所以可能无法声明数组is
。相反,您可以使用 std::tuple
和 print 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
}
};
上面的模式(创建元组然后处理元组)允许您将一些复杂的函数应用于参数包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
}
};