抽象非类型模板参数的类型

gla*_*erl 6 c++ templates

我想编写一个模板,可以将类型解析为具有非类型模板参数的模板及其非类型模板参数.例如,它将解构Array<5>template<int> Array5,但一般会用于任何类型的非类型模板参数(整数类型,指针,成员指针等).

首先尝试,使用模板专业化:

template<typename T> struct foo { enum { n = 1 }; };

template<int x> struct bar { enum { n = x }; };

template<typename T, template<T> class X, T x>
struct foo< X<x> > { enum { n = x }; }; // here x must be of integral type, but that's just for testing

int main(int, char**) { return foo< bar<16> >::n; }
Run Code Online (Sandbox Code Playgroud)

Clang 3.1说:

test145.cpp:6:8: warning: class template partial specialization contains a template parameter that can not be deduced; this partial specialization will never be used
struct foo< X<x> > { enum { n = x }; };
       ^~~~~~~~~~~
test145.cpp:5:19: note: non-deducible template parameter 'T'                     
template<typename T, template<T> class X, T x>
                  ^
1 warning generated.
Run Code Online (Sandbox Code Playgroud)

第二次尝试,使用功能模板:

template<typename T, T x> 
struct box 
{ 
    static constexpr T value() { return x; }
};

template<typename T, template<T> class X, T x>
box<T, x> foo(X<x>);

template<int> struct asdf { };

int main(int, char**) { return decltype(foo(*(asdf<9>*)0))::value(); }
Run Code Online (Sandbox Code Playgroud)

Clang说:

test150.cpp:12:41: error: no matching function for call to 'foo'
int main(int, char**) { return decltype(foo(*(asdf<9>*)0))::value(); }
                                        ^~~
test150.cpp:8:11: note: candidate template ignored: couldn't infer template argument 'T'
box<T, x> foo(X<x>);
          ^
1 error generated.
Run Code Online (Sandbox Code Playgroud)

GCC 4.7说了类似的话.

这是一个基本限制吗?

额外问题:如果是,那么有没有办法处理有限数量的代码中的所有无限可能性,即使它不那么简单和通用代码?(例如指针变得困难:出于同样的原因,你似乎无法写作,template<T>我认为你也不能写template<T*>.)

请不要问我为什么要问.

小智 3

答案可能来得有点晚了……

\n

错过尝试...

\n

(参见下面的正确答案C++17 解决方案

\n
\n
\n

这个原始答案被保留作为我对 SO 的第一个答案的纪念品。
\n有人会说,这并不完全是失败。相反,第一次错过了尝试... ;)
\n现在,跳到下一条水平线...

\n
\n

当我遇到这个问题时,我正在寻找相关问题的答案。读完后,我告诉自己:“嗯……那是我已经做过的事情。而且它有效。我到底是怎么做到的?!” 。然后,我继续寻找我的问题的答案......

\n

今天我觉得我应该花一点时间来提出这个问题的解决方案(实际上是两个)

\n

正如您已经注意到的,问题来自于编译器不知道如何推导T. 人们可以将错误消息解释为“请给我一点帮助T

\n

我所做的第一个正在运行的版本具有foo从类似于std::integral_constant. 进行foo派生std::integral_constant<T, x>可能有助于编译器确定T. (或者也许 MSVC -vs2019- 对我来说有点好)

\n

不管怎样,同时我找到了更好的解决方案。并且编译器应该没有办法无法推断出 的类型T,因为,不需要...typename T类型的参数。x

\n
\n

这是:(C++17 解决方案)

\n
template<typename T> struct foo {};\n\ntemplate<auto x, template<decltype(x)> class X>\nstruct foo<X<x>> {\n    using            arg_type   = decltype(x);\n    static constexpr arg_type n = x;\n};\n\n//template<int x> struct bar { enum { n = x }; };\ntemplate<int x> struct bar;\n\nusing bar_16            = foo<bar<16>>;\nusing bar_16_arg_t      = typename bar_16::arg_type; // int\nconstexpr auto bar_16_n = bar_16::n;                 // 16\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,要使其起作用,甚至不需要它是bar完整的类型。前向声明(如本示例中所示)足以使分解正常进行。

\n

享受...

\n
\n

正确答案

\n

\xc2\xb0 注释

\n
\n
    \n
  • 回答了 9 年前提出的问题并且
  • \n
  • 这里提出的解决方案仅使用C++11 功能
  • \n
  • 该解决方案仅管理整数类型
    \n (其他类型作为读者的练习)
    \n
    \n如果您的编译器支持C++17功能,则应首选上面发布的解决方案,因为它不仅管理整数类型
  • \n
\n
\n

只对工作代码示例感兴趣?
\n跳转到:“工作解决方案

\n

\xc2\xb0 前言

\n
\n
    \n
  • 经过我的研究,似乎到目前为止还没有找到(或没有发布)这个特定问题的解决方案。我想我应该更详细地了解“为什么”“如何”我希望它会受到赞赏,但总的来说:有用......
  • \n
  • 我目前正在编写一个充满编译时工具和功能的元编程库。我希望能够尽快在 GitHub 上发布它。
    \n (谁知道)\xe2\x80\x93 无论如何......
  • \n
  • 当我意识到我的第一个答案仅自C++17以来才是正确的时,我的挫败感还没有... \xe2\x80\x93不完全“准时”,有人可以说...:P
  • \n
  • 考虑到我心目中的 ATM 具有“仅限编译时”功能的所有机制,我感觉 9 年前就应该有一种方法可以做到这一点。
  • \n
  • 我开始思考如何仅使用C++11功能来做到这一点,大约一个小时后,我找到了一个可行的解决方案(其实是两个)
  • \n
  • 我花了更多的时间才使它成为一个可用的解决方案(其实是两个)
  • \n
  • 还有相当多的内容来纠正这篇文章... :D
    \n
    \n毕竟,可能有编译器
    \n “足够好”来理解C++11 ... :P
  • \n
\n
\n

显然,由于当时可用的功能集更窄,
找到的解决方案稍微更冗长...... :D

\n

\xc2\xb0 搜索过程

\n

首先,必须记住,当编译器输出无法推断 ...
\n\xe2\x80\x93 时,并不意味着存在错误(尽管可能有错误)
\n\xe2\x80\x93 这意味着编译器并不像人们想象的那么聪明。
\n\xe2\x80\x93 这意味着必须向编译器伸出援手,让它能够完成其工作......

\n

清楚吗?
\n\xe2\x80\x93 编译器恳请您完成其部分工作。
\n\xe2\x80\x93 你有很好的机会:

\n
    \n
  • 最终自己完成大部分工作...... :P
  • \n
\n

在这里,编译器说“无法推断出”的类型T
\n事实上,T没有用在用作 的特化参数的表达式中foo,因此,不能从那里推导出来......

\n

首先必须做一些事情来表示typename T和 的值x(类型为T)之间的关系。人们立即想到的是,我们需要一个类似于它的模板来std::integral_constant实现这一点。它将一个值及其相应的类型编码为一种新类型。

\n
\n

免责声明[! 警告!]

\n
    \n
  • 容易在看到标识符名称中的大写字母时产生过敏反应的人不应继续阅读这篇文章!
  • \n
\n
\n

在那之前没有什么新的东西吗?
\n完美!这里是:

\n
template<typename T, T V>\nstruct NonTypeParam { using Type = T; static constexpr T Value = V; };\n
Run Code Online (Sandbox Code Playgroud)\n

接下来需要一些东西来创建NonTypeParam具有值及其相应类型的模板实例......

\n
    \n
  • 它可能是一个带有类型参数的模板。
  • \n
  • 该参数将接收要分解的类型。
  • \n
  • 然后,人们必须以某种方式专门化它......
  • \n
\n

让我们尝试一下,然后开始:

\n
template<typename T> struct Extract { using Result = void; };\n
Run Code Online (Sandbox Code Playgroud)\n

要完全抽象模板的专业化Extract,必须编写如下内容:

\n
template<typename T, T V, template<T> class C>\nstruct Extract<C<V>> { using Result = NonTypeParam<T, V>; };\n
Run Code Online (Sandbox Code Playgroud)\n

这会导致相同的问题,因为它是问题中使用的相同类型的专业化。此时,我们必须提醒编译器无法执行哪些操作。它无法推断出参数T在我们的专业化中应该别名的类型......

\n

事实上,该消息在某种程度上具有误导性,因为T它甚至不是作为专业化参数传递的表达式的一部分。因此,问题不在于将 a 归因typename于参数T,而在于将类型归因于参数V...
\n现在,人们应该能够提出正确的问题:

\n
    \n
  1. 如何T从方程中删除?\n
      \n
    • 通过显式定义 的类型V
    • \n
    \n
  2. \n
  3. 值的可能类型有哪些V?\n
      \n
    • 允许作为非类型模板参数的类型。
    • \n
    \n
  4. \n
\n

首先,例如,通过显式定义Vfor的类型char,专业化会是什么样子?它看起来像这样:

\n
template<char V, template<char> class C>\nstruct Extract<C<V>> { using Result = NonTypeParam<char, V>; };\n
Run Code Online (Sandbox Code Playgroud)\n

,这有点烦人,但因为可能性是有限的。人们稍后可能会找到一种减少声明的方法。让我们添加另一个专业化,一个受害者模板,然后对其进行测试......

\n
template<typename T, T V>\nstruct NonTypeParam { using Type = T; static constexpr T Value = V; };\n\ntemplate<typename T> struct Extract { using Result = void; };\n\ntemplate<char V, template<char> class C>\nstruct Extract<C<V>> { using Result = NonTypeParam<char, V>; };\n\ntemplate<std::size_t V, template<std::size_t> class C>\nstruct Extract<C<V>> { using Result = NonTypeParam<std::size_t, V>; };\n\ntemplate<std::size_t I> struct TestNonType1 {};\n\nusing Result          = typename Extract<TestNonType1<42>>::Result;\nusing RType           = typename Result::Type; // std::size_t\nconstexpr auto rValue = Result::Value;         // 42\n
Run Code Online (Sandbox Code Playgroud)\n

不出意外,它按预期工作...
\n... 现在可能的类型是什么?\n根据模板参数
标准:

\n
\n

非类型模板参数必须具有结构类型,该类型是以下类型之一(可选 cv 限定,限定符被忽略)

\n
    \n
  • 左值引用类型(对象或函数);
  • \n
  • 整型;
  • \n
  • 指针类型(指向对象或函数);
  • \n
  • 指向成员类型(指向成员对象或成员函数)的指针;
  • \n
  • 枚举类型;
  • \n
  • std::nullptr_t; (自 C++11 起)
  • \n
\n
\n

对于我们的例子,问题要求整数类型
\n那么,标准对整数类型有何规定。
\n让我们看一下std::is_integral来找出答案:

\n
\n

..., ifT是类型boolcharchar8_t (C++20 起)char16_tchar32_twchar_tshortintlong、 、long long任何实现定义的扩展整数类型,包括任何有符号无符号cv 限定的变体。

\n
\n

哎哟!

\n

由于存在 9 种类型 \xe2\x80\x93,如果排除char8_t (仅从 C++20 中)并考虑实现定义的整数类型大多数时候是这些整型类型\xe2\x80\x93 的别名,则必须专攻:

\n
    \n
  • 9 signed.
  • \n
  • 9 signed const.
  • \n
  • 9 signed volatile.
  • \n
  • 9 signed const volatile.
  • \n
  • 其中有36个专业。
  • \n
  • 然后,为未签名版本再添加 36 个?!
  • \n
\n
\n

免责声明[通知]

\n
    \n
  • 毫无疑问,这就是为什么以前没有人(也许真的没有其他人)这样做的原因......
  • \n
\n
\n
\n

等等,等等,等一下……

\n

人们应该再次思考这一点,并再次提出正确的问题:

\n
    \n
  • 非类型参数如何“读取” / “解释”
  • \n
  • 这样做有什么意义吗volatile
  • \n
  • 如果它的值是 的一部分typename,那么是否const隐含了某种原因
  • \n
\n

你肯定自己找到了答案......

\n

\xe2\x80\x93 同样,没有,unsignedchar16_tchar32_t的版本wchar_t
\n\xe2\x80\x93 此外,如果人们再仔细阅读标准中关于模板参数的内容,人们可能会看到一些没有得到应有关注的东西......

\n
\n

非类型模板参数必须具有结构类型,它是以下类型之一(可选 cv 限定,限定符被忽略

\n
\n

好啦好啦……

\n

\xe2\x80\x93 这将比一开始排除的工作多得多... :P \n\xe2\x80\x93 事实证明,最终,模板只有 14 个特化就足够了管理99% 的所有可能的积分类型...
Extract

\n

...我认为对于如此少量的代码来说,写得太多了。

\n

请在下面找到解决方案, \xe2\x80\x93让后代\xe2\x80\x93 希望它对某人有用至少对于第二个示例中使用的有趣的“欺骗”)

\n

\xc2\xb0 个人评论

\n
\n

我很难相信这个 9 年前的问题还没有更早找到答案(并且认为我将是唯一找到这个答案的“愚蠢”家伙)

\n
\n
\n

工作解决方案

\n

解决方案#1

\n
\n

这里没什么特别的。这只是模板的常规专业化......

\n
\n
template<typename T, T V>\nstruct NonTypeParam { using Type = T; static constexpr T Value = V; };\n\nnamespace Details1 {\n\ntemplate<typename T> struct Extract { using Result = void; };\n\ntemplate<typename T, T V> using R = NonTypeParam<T, V>;\n\n// boolean\ntemplate<bool V, template<bool> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\n// signed types\ntemplate<char      V, template<char>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<char16_t  V, template<char16_t>  class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<char32_t  V, template<char32_t>  class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<wchar_t   V, template<wchar_t>   class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<short     V, template<short>     class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<int       V, template<int>       class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<long      V, template<long>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<long long V, template<long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\n// unsigned types\ntemplate<unsigned char      V, template<unsigned char>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<unsigned short     V, template<unsigned short>     class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<unsigned int       V, template<unsigned int>       class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<unsigned long      V, template<unsigned long>      class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\ntemplate<unsigned long long V, template<unsigned long long> class C> struct Extract<C<V>> { using Result = R<decltype(V), V>; };\n\n} /* namespace Details1 */\n\ntemplate<typename T>\nstruct Extract1\n{\n    using Result = typename Details1::Extract<T>::Result;\n};\n\n// Victim template:\ntemplate<std::size_t I> struct TestNonType1 {};\n\n// Usage:\nusing          Param  = typename Extract1<TestNonType1<42>>::Result;\nusing          PType  = typename Param::Type; // std::size_t\nconstexpr auto pValue = Param::Value;         // 42\n
Run Code Online (Sandbox Code Playgroud)\n

解决方案#2

\n
\n

在此解决方案中,我们利用decltype声明函数模板重载的功能,该重载永远不会在任何地方定义......

\n
\n
template<typename T, T V>\nstruct NonTypeParam { using Type = T; static constexpr T Value = V; };\n\nnamespace Details2 {\n\ntemplate<typename T, T V> using R = NonTypeParam<T, V>;\n\n// boolean\ntemplate<bool V, template<bool> class C> R<decltype(V), V> Extract(C<V> && _);\n// signed types\ntemplate<char      V, template<char>      class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<char16_t  V, template<char16_t>  class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<char32_t  V, template<char32_t>  class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<wchar_t   V, template<wchar_t>   class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<short     V, template<short>     class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<int       V, template<int>       class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<long      V, template<long>      class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<long long V, template<long long> class C> R<decltype(V), V> Extract(C<V> && _);\n// unsigned types\ntemplate<unsigned char      V, template<unsigned char>      class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<unsigned short     V, template<unsigned short>     class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<unsigned int       V, template<unsigned int>       class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<unsigned long      V, template<unsigned long>      class C> R<decltype(V), V> Extract(C<V> && _);\ntemplate<unsigned long long V, template<unsigned long long> class C> R<decltype(V), V> Extract(C<V> && _);\n\n} /* namespace Details2 */\n\ntemplate<typename T>\nstruct Extract2\n{\n    using Result = decltype(Details2::Extract(std::declval<T>()));\n};\n\n// Victim template:\ntemplate<unsigned long long I> struct TestNonType2 {};\n\n// Usage:\nusing          Param  = typename Extract2<TestNonType2<42>>::Result;\nusing          PType  = typename Param::Type; // std::size_t\nconstexpr auto pValue = Param::Value;         // 42\n
Run Code Online (Sandbox Code Playgroud)\n
\n

\xc2\xb0 更新(2021 年 7 月 25 日)

\n
    \n
  • 下面是如何分解使用任何类型的非类型参数声明的模板的示例。
  • \n
  • 不幸的是,虽然这一小段代码似乎只使用了 C++11 语言功能,但它无法编译为 C++11。
  • \n
  • 这段代码工作完美,并且完成了它应该做的事情,但必须编译为 C++17。
  • \n
  • 标准肯定发生了变化,因为添加了auto作为非类型模板参数,我认为(但无法找到有关它的信息),使编译器将模式解释<typename T, template <T> class C, T V>为好像它是的同义词<auto V>
  • \n
\n
/* Template allowing to separately retrieve the components\n * of a template having one non-type parameter.\n */\ntemplate<typename T, template <T> class C, T V>\nstruct TmplInfo;\n\n/* Function to decompose a template having one non-type\n * parameter and return its corresponding TmplInfo type.\n */\ntemplate<typename T, template <T> class C, T V>\ninline constexpr TmplInfo<T, C, V> ToTmplInfo(C<V> && o);\n\n/* Our victim template...\n */\ntemplate<std::size_t I> struct Victim;\n\n/* Aliases Victim<42> and then decompose it to a TmplInfo.\n */\nusing V42   = Victim<42>;\nusing VInfo = decltype(ToTmplInfo(std::declval<V42>()));\n\n/* Compiled for x64 arch, this gives:\n * using VInfo = TmplInfo<std::size_t, Victim, 42Ui64>;\n */\n
Run Code Online (Sandbox Code Playgroud)\n