答案可能来的有点晚……
错过尝试...
(请参阅下面的正确答案和C++17 解决方案)
这个原始答案保留为我对 SO 的第一个答案的纪念品。
不完全是失败,有人会说。而是第一次错过的尝试... ;)
现在,跳到下一条水平线...
当我遇到这个问题时,我正在寻找相关问题的答案。读完之后,我告诉自己:“嗯……那是我已经做过的事情。而且它奏效了。我到底是怎么做到的?!” . 然后,我继续寻找我的问题的答案......
今天我觉得我应该花一点时间来提出这个问题的解决方案(实际上是两个)。
正如您已经注意到的那样,问题来自编译器不知道如何推断的事实T
。可以将错误消息解释为“请给我一点帮助T
”。
我所做的第一个工作版本具有foo
从类似于std::integral_constant
. 派生可能有助于编译foo
器std::integral_constant<T, x>
找出T
. (或者也许 MSVC -vs2019- 对我有点好)
无论如何,同时我找到了更好的解决方案。并且编译器应该没有办法不能推断出 的类型,因为...的类型T
不需要typename T
参数x
这里是:(C++17 解决方案)
template<typename T> struct foo {};
template<auto x, template<decltype(x)> class X>
struct foo<X<x>> {
using arg_type = decltype(x);
static constexpr arg_type n = x;
};
//template<int x> struct bar { enum { n = x }; };
template<int x> struct bar;
using bar_16 = foo<bar<16>>;
using bar_16_arg_t = typename bar_16::arg_type; // int
constexpr auto bar_16_n = bar_16::n; // 16
请注意,要使其工作,它甚至不需要bar
成为一个完整的类型。前向声明(如本例所示)足以让分解工作。
享受...
正确答案
° 备注
- 这回答了 9 年前提出的问题,并且,
- 此处提出的解决方案仅使用 C++11 特性。
- 此解决方案仅管理整数类型。
(让其他类型作为读者练习)
如果您的编译器支持C++17功能,则应该首选上面发布的解决方案,因为它不仅管理整数类型。
只对工作代码示例感兴趣?
跳转到:“工作解决方案”
° 序言
- 经过我的研究,似乎直到现在还没有找到(或没有发表)这个特定问题的解决方案。我想我应该详细介绍一下“Whys”和“Hows”。我希望它会受到赞赏,但总的来说:有用...
- 我目前正在编写一个包含编译时工具和功能的元编程库。我希望能够很快在 GitHub 上发布它。
(谁知道)——无论如何……
- 当我意识到我的第一个答案仅在C++17之后才正确时,我并没有感到沮丧...... –不完全是“准时”,有人可以说......:P
- 有了我想到的 ATM 的所有“仅编译时”特性的机制,我觉得 9 年前应该有办法做到这一点。
- 我开始思考如果只使用C++11功能我会如何做到这一点,大约一个小时后,我找到了一个可行的解决方案。(实际上是两个)
- 我花了一点时间使它成为一个可用的解决方案。(实际上是两个)
- 还有很多东西要纠正这篇文章...... :D
毕竟,可能有编译器
“足够好”来理解C ++ 11 ...... :P
显然,由于当时可用的功能集更窄,
找到的解决方案“只是有点”更冗长...... :D
° 搜索过程
首先,必须记住,当编译器输出“无法推断”时……
这并不意味着存在错误(尽管可能有错误)。
– 这意味着编译器并不像人们想象的那么聪明。
– 这意味着一个人必须帮助编译器才能完成它的工作......
清楚吗?
– 编译器恳请您完成其工作的一部分。
– 你有很好的机会:
在这里,编译器说“无法推断”的类型T
。
确实,T
不用于作为 的特化的参数的表达式foo
,因此,它不能从那里推导出来......
首先必须做一些事情来表示(类型为)之间的关系typename T
和值。立即想到的是,人们需要一个类似的模板,它正是这样做的。它将一个值及其对应的类型编码为一个新的类型。x
T
std::integral_constant
免责声明[! 警告!]
- 看到标识符名称中的大写字母容易产生过敏反应的人不应该继续阅读这篇文章!
在那之前没有什么新鲜事吗?
完美的!这里是:
template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };
接下来需要一些东西来创建NonTypeParam
具有值及其对应类型的模板实例......
- 它可能是一个带有类型参数的模板。
- 此参数将接收要分解的类型。
- 然后,必须以某种方式对其进行专门化...
让我们试一试,从以下开始:
template<typename T> struct Extract { using Result = void; };
要完全抽象Extract
模板的专业化,必须编写如下内容:
template<typename T, T V, template<T> class C>
struct Extract<C<V>> { using Result = NonTypeParam<T, V>; };
这会导致同样的问题,因为它与问题中使用的专业类型相同。在这一点上,人们不得不提醒编译器不能做什么。它“无法推断”T
在我们的专业化中参数应该是什么类型的别名......
事实上,该消息在某种程度上具有误导性,因为T
它甚至不是作为专业化参数传递的表达式的一部分。因此,问题不在于将 a 归因typename
于参数T
,而是将类型归因于参数V
......
现在,应该能够提出正确的问题:
- 怎样才能
T
从方程中去掉?
- 的值有哪些可能的类型
V
?
首先,例如,通过显式定义V
for的类型,将如何看待专业化?char
它看起来像这样:
template<char V, template<char> class C>
struct Extract<C<V>> { using Result = NonTypeParam<char, V>; };
,这有点烦人,但由于可能性有限。以后可能会找到一种减少声明的方法。让我们添加另一个专业化,一个受害者模板,并对其进行测试......
template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };
template<typename T> struct Extract { using Result = void; };
template<char V, template<char> class C>
struct Extract<C<V>> { using Result = NonTypeParam<char, V>; };
template<std::size_t V, template<std::size_t> class C>
struct Extract<C<V>> { using Result = NonTypeParam<std::size_t, V>; };
template<std::size_t I> struct TestNonType1 {};
using Result = typename Extract<TestNonType1<42>>::Result;
using RType = typename Result::Type; // std::size_t
constexpr auto rValue = Result::Value; // 42
毫不奇怪,它按预期工作......
现在可能的类型是什么?
根据模板参数标准:
非类型模板参数必须具有结构类型,它是以下类型之一(可选 cv 限定,限定符被忽略):
- 左值引用类型(对象或函数);
- 整数类型;
- 指针类型(指向对象或函数);
- 指向成员类型的指针(指向成员对象或成员函数);
- 枚举类型;
- std::nullptr_t; (C++11 起)
对于我们的案例,问题要求整数类型。
好吧,标准对整数类型有什么看法。
让我们看一下std::is_integral以找出答案:
..., ifT
是类型bool
, char
, char8_t
(C++20 起) , char16_t
, char32_t
, wchar_t
, short
, int
, long
, long long
, 或任何实现定义的扩展整数类型,包括任何有符号、无符号和cv 限定的变体。
哎哟!
由于有 9 种类型——如果一种排除char8_t
(仅来自 C++20)并认为实现定义的整数类型在大多数情况下是这些整数类型的别名——则必须对以下内容进行专门化:
- 9
signed
.
- 9
signed const
.
- 9
signed volatile
.
- 9
signed const volatile
.
- 其中有36个专业。
- 然后,为未签名的版本再添加 36 个?!
免责声明[ 通知 ]
- 毫无疑问,这就是为什么以前没有人(也许真的没有人)这样做的原因......
等等,等等,等一下……
人们应该再次考虑这一点,并再次提出正确的问题:
- 非类型参数如何“读取” / “解释” ?
- 这样做有什么意义吗?
volatile
- 如果它的值是 的一部分
typename
,是不是const
以某种方式隐含?
你肯定自己找到了答案……
– 同样,没有和unsigned
的版本。
– 此外,如果您再仔细阅读标准中有关模板参数的内容,您可能会看到一些没有得到应有注意的东西......char16_t
char32_t
wchar_t
非类型模板参数必须具有结构类型,它是以下类型之一(可选 cv 限定,限定符被忽略)
好,好,好……
– 这将比一开始会做更多的工作...... :P
– 最终,只有模板的14 个专业化足以管理99% 的所有可能的整数类型......
Extract
...我认为对于如此少量的代码来说,这太多了。
请在下面找到解决方案 -让后代在这里 - 希望它可能对某人有用(至少对于第二个示例中使用的有趣的“诡计”)。
° 个人评论
我很难相信这个 9 年前的问题还没有更早地找到答案(以及认为我会是唯一一个找到这个答案的“愚蠢”人)
工作解决方案
解决方案#1
这里没什么特别的。这只是模板的常规专业化......
template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };
namespace Details1 {
template<typename T> struct Extract { using Result = void; };
template<typename T, T V> using R = NonTypeParam<T, V>;
// boolean
template<bool V, template<bool> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
// signed types
template<char V, template<char> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<char16_t V, template<char16_t> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<char32_t V, template<char32_t> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<wchar_t V, template<wchar_t> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<short V, template<short> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<int V, template<int> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<long V, template<long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<long long V, template<long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
// unsigned types
template<unsigned char V, template<unsigned char> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned short V, template<unsigned short> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned int V, template<unsigned int> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned long V, template<unsigned long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
template<unsigned long long V, template<unsigned long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };
} /* namespace Details1 */
template<typename T>
struct Extract1
{
using Result = typename Details1::Extract<T>::Result;
};
// Victim template:
template<std::size_t I> struct TestNonType1 {};
// Usage:
using Param = typename Extract1<TestNonType1<42>>::Result;
using PType = typename Param::Type; // std::size_t
constexpr auto pValue = Param::Value; // 42
解决方案#2
在这个解决方案中,一个人利用decltype
声明函数模板重载的力量,它永远不会在任何地方定义......
template<typename T, T V>
struct NonTypeParam { using Type = T; static constexpr T Value = V; };
namespace Details2 {
template<typename T, T V> using R = NonTypeParam<T, V>;
// boolean
template<bool V, template<bool> class C> R<decltype(V), V> Extract(C<V> && _);
// signed types
template<char V, template<char> class C> R<decltype(V), V> Extract(C<V> && _);
template<char16_t V, template<char16_t> class C> R<decltype(V), V> Extract(C<V> && _);
template<char32_t V, template<char32_t> class C> R<decltype(V), V> Extract(C<V> && _);
template<wchar_t V, template<wchar_t> class C> R<decltype(V), V> Extract(C<V> && _);
template<short V, template<short> class C> R<decltype(V), V> Extract(C<V> && _);
template<int V, template<int> class C> R<decltype(V), V> Extract(C<V> && _);
template<long V, template<long> class C> R<decltype(V), V> Extract(C<V> && _);
template<long long V, template<long long> class C> R<decltype(V), V> Extract(C<V> && _);
// unsigned types
template<unsigned char V, template<unsigned char> class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned short V, template<unsigned short> class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned int V, template<unsigned int> class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned long V, template<unsigned long> class C> R<decltype(V), V> Extract(C<V> && _);
template<unsigned long long V, template<unsigned long long> class C> R<decltype(V), V> Extract(C<V> && _);
} /* namespace Details2 */
template<typename T>
struct Extract2
{
using Result = decltype(Details2::Extract(std::declval<T>()));
};
// Victim template:
template<unsigned long long I> struct TestNonType2 {};
// Usage:
using Param = typename Extract2<TestNonType2<42>>::Result;
using PType = typename Param::Type; // std::size_t
constexpr auto pValue = Param::Value; // 42
° 更新(2021 年 7 月 25 日)
- 下面是如何分解使用任何类型的非类型参数声明的模板的示例。
- 不幸的是,虽然这小段代码似乎只使用了 C++11 语言特性,但它不能编译为 C++11。
- 这段代码完美地工作,并且做了它应该做的事情,但必须编译为 C++17。
auto
自从添加作为非类型模板参数以来,标准肯定发生了变化,我认为(但找不到关于它的信息),使编译器将模式解释<typename T, template <T> class C, T V>
为.<auto V>
/* Template allowing to separately retrieve the components
* of a template having one non-type parameter.
*/
template<typename T, template <T> class C, T V>
struct TmplInfo;
/* Function to decompose a template having one non-type
* parameter and return its corresponding TmplInfo type.
*/
template<typename T, template <T> class C, T V>
inline constexpr TmplInfo<T, C, V> ToTmplInfo(C<V> && o);
/* Our victim template...
*/
template<std::size_t I> struct Victim;
/* Aliases Victim<42> and then decompose it to a TmplInfo.
*/
using V42 = Victim<42>;
using VInfo = decltype(ToTmplInfo(std::declval<V42>()));
/* Compiled for x64 arch, this gives:
* using VInfo = TmplInfo<std::size_t, Victim, 42Ui64>;
*/