答案可能会有点晚...
错过的尝试...
(请参见下面的正确答案和C++17解决方案)
这篇原始答案被保留作为我在SO上的第一个回答的纪念。
不算是失败,可以说是第一次尝试失败了... ;)
现在,跳到下一行...
当我在寻找相关问题的答案时,我偶然遇到了这个问题。读完后,我告诉自己:
"嗯...我已经做过这件事了。而且它起作用了。我当时怎么做的?!"。然后,我继续寻找我的问题的答案...
今天我觉得应该花点时间提出这个问题的解决方案
(实际上是两个)。
正如您已经注意到的那样,问题在于编译器不知道如何推断
T
。人们可以将错误消息解释为
"请给我一点关于T
的帮助"。
我做的第一个工作版本是具有类似于std::integral_constant的类的foo专业化。让foo派生自std::integral_constant可能有助于编译器找出T的类型。(或者也许MSVC-vs2019-对我友好了一点) 无论如何,与此同时,我找到了更好的解决方案。编译器不应该无法推断出T的类型,因为x的类型不需要typename T参数...
这里是:(C++17 解决方案)
template<typename T> struct foo ;
template<auto x, template<decltype(x)> class X>
struct foo<X<x>> ;
template<int x> struct bar;
using bar_16 = foo<bar<16>>;
using bar_16_arg_t = typename bar_16::arg_type;
constexpr auto bar_16_n = bar_16::n;
请注意,为了使此方法起作用,甚至不需要
bar
是一个完整的类型。仅仅一个前向声明
(就像这个例子中)就足够进行
分解操作。
祝愉快...
正确答案
° 注意事项
- 这是对9年前问题的回答,并且,
- 此处提出的解决方案仅使用C++11功能。
- 此解决方案仅管理整数类型。
(其他类型留给读者练习)
如果您的编译器支持C++17功能,则应首选上面发布的解决方案,因为它可以管理不仅仅是整数类型。
只对工作代码示例感兴趣?
跳转至: "工作解决方案"
° 前言
在我的研究中,似乎直到现在为止,这个特定问题的解决方案还没有被发现(或者没有公开)。我认为我应该更详细地说明一下“为什么”和“如何”。希望这会得到赞赏,但总体来说:很有用...
我目前正在编写一个元编程库,其中包含许多编译时工具和功能。我希望能够尽快在GitHub上发布它。(谁知道呢)-无论如何...
当我意识到我的第一个答案只有在C++17之后才正确时,我的挫败感是无与伦比的...-可以说并不“准时”...:P
我心里想着,使用我现在所拥有的所有“仅编译时”的功能,我感觉9年前就应该有一种方法可以做到。
我开始思考如何使用仅C++11功能来完成此操作,大约一个小时后,我找到了一个可行的解决方案。(事实上有两个)
花费了我更多的时间来使其成为一个可用的解决方案。(事实上有两个)
写这篇文章花费了相当长的时间...:D
毕竟,可能有编译器“只好”理解仅限于C++11...:P
显然,由于当时可用的功能集更窄,所以找到的解决方案会更加详细...:D
° 搜索过程
首先,必须记住,当编译器输出 "无法推断" 时...
- 这并不意味着有错误(虽然可能有错误)。
- 它实际上意味着编译器没有人们想象的那么聪明。
- 这意味着需要给编译器提供帮助,以便它能够完成其工作...
清楚了吗?
- 编译器在友好地要求您完成其工作的一部分。
- 并且您有很大的机会:
在这里,编译器说“无法推断出T
的类型”。
确实,在用作foo
专业化的参数表达式中未使用T
,因此无法从中推断出T
...
首先,需要做一些事情来表示 typename T
和类型为 T
的值 x
之间的关系。想到的是需要一个类似于 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甚至不是作为参数传递给专业化的一部分。因此,问题不在于将typename归属于参数T,而在于将类型归属于参数V...现在,应该能够提出正确的问题:
1. 如何从等式中删除T?
- 通过明确定义V的类型。
2. V的可能类型是什么?
- 允许作为非类型模板参数的类型。
首先要做的是,例如,如何通过显式定义V的类型来查看char的专业化呢?它看起来像这样:
template<char V, template<char> class C>
struct Extract<C<V>> { using Result = NonTypeParam<char, V>; };
这有点让人烦恼,但由于可能性有限,我们可以找到一种方法来减少后续的声明。让我们再添加一个专门的模板——受害者模板,并进行测试...
template<typename T, T V>
struct NonTypeParam ;
template<typename T> struct Extract ;
template<char V, template<char> class C>
struct Extract<C<V>> ;
template<std::size_t V, template<std::size_t> class C>
struct Extract<C<V>> ;
template<std::size_t I> struct TestNonType1 ;
using Result = typename Extract<TestNonType1<42>>::Result;
using RType = typename Result::Type;
constexpr auto rValue = Result::Value;
没有意外,它按预期工作...
... 现在可能有哪些类型?
根据
模板参数标准:
非类型模板参数必须具有结构类型,这是以下类型之一(可选择cv限定符,忽略限定符):
- 左值引用类型(对对象或函数);
- 整数类型;
- 指针类型(对对象或函数);
- 成员指针类型(对成员对象或成员函数);
- 枚举类型;
- std::nullptr_t; (自C++11起)
对于我们的情况,问题要求整数类型。
那么,标准对整数类型有什么说法呢?
让我们看看std::is_integral以找出答案:
..., 如果 T
是类型 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
是否会在某种程度上被隐含?
你肯定自己找到了答案...
同样地,char16_t
、char32_t
和wchar_t
没有无符号版本。
此外,如果仔细阅读标准中关于模板参数的内容,可能会发现一些值得关注的东西...
非类型模板参数必须具有结构类型,这些类型是以下类型之一(可选择cv限定符,忽略修饰符)
嗯,嗯,嗯...
- 这将比一个人最初期望的做更多的工作...:P
- 最终只需要14个Extract
模板的特化就足以管理99%的所有可能的整数类型...
...我认为这对于如此少量的代码来说太多了。
请在下面找到解决方案,- 留给后代 - 希望它对某人有用(至少对第二个示例中使用的有趣的“诡计”有用)。
° 个人评论
我很难相信这个九年前的问题没有早些时候得到答案 (也认为我不会是唯一一个找到答案的“蠢人”)
工作解决方案
解决方案 #1
这里没有什么特别的。它只是一个模板的常规专业化...
template<typename T, T V>
struct NonTypeParam ;
namespace Details1 ;
template<typename T, T V> using R = NonTypeParam<T, V>;
template<bool V, template<bool> class C> struct Extract<C<V>> ;
template<char V, template<char> class C> struct Extract<C<V>> ;
template<char16_t V, template<char16_t> class C> struct Extract<C<V>> ;
template<char32_t V, template<char32_t> class C> struct Extract<C<V>> ;
template<wchar_t V, template<wchar_t> class C> struct Extract<C<V>> ;
template<short V, template<short> class C> struct Extract<C<V>> ;
template<int V, template<int> class C> struct Extract<C<V>> ;
template<long V, template<long> class C> struct Extract<C<V>> ;
template<long long V, template<long long> class C> struct Extract<C<V>> ;
template<unsigned char V, template<unsigned char> class C> struct Extract<C<V>> ;
template<unsigned short V, template<unsigned short> class C> struct Extract<C<V>> ;
template<unsigned int V, template<unsigned int> class C> struct Extract<C<V>> ;
template<unsigned long V, template<unsigned long> class C> struct Extract<C<V>> ;
template<unsigned long long V, template<unsigned long long> class C> struct Extract<C<V>> ;
} /* namespace Details1 */
template<typename T>
struct Extract1
;
template<std::size_t I> struct TestNonType1 ;
using Param = typename Extract1<TestNonType1<42>>::Result;
using PType = typename Param::Type;
constexpr auto pValue = Param::Value;
解决方案 #2
在此解决方案中,使用decltype
声明函数模板重载的能力,这些函数模板将永远不会被定义...
template<typename T, T V>
struct NonTypeParam ;
namespace Details2 /* namespace Details2 */
template<typename T>
struct Extract2
;
template<unsigned long long I> struct TestNonType2 ;
using Param = typename Extract2<TestNonType2<42>>::Result;
using PType = typename Param::Type;
constexpr auto pValue = Param::Value;
° 更新(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>;
*/
template<int> struct A {}
。是否可能(如果可能,如何)编写一个模板arg
,使得arg<Array<5> >::template_
是template<int> Array
,arg<Array<5> >::type
是int
,arg<Array<5> >::value
是5
(且类型为int
),并且使其通用化,以便以这种方式处理每个可能的非类型模板参数。 - reima