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

时间:2012-05-25 18:57:36

标签: c++ templates

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

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

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

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.

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

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

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.

GCC 4.7也有类似的说法。

这是一个基本限制吗?

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

请不要问我为什么要问。

2 个答案:

答案 0 :(得分:2)

除了模板类型参数,而不是模板非类型参数之外,另一个问题是基本相同的问题:template metaprogramming: (trait for?) dissecting a specified template into types T<T2,T3 N,T4, ...>

对于类型参数,它确实很容易。代码如下所示:

#include <tuple>
#include <vector>

template <class T> struct explode;

template <template <class... Args> class T, class... N>
struct explode<T<N...>>
{
    typedef T<N...> type;
    template <class... Args> using template_ = T<Args...>;
    template <int I> using type_parameter =
        typename std::tuple_element<I, std::tuple<N...>>::type;
};

#if TESTING
void test_harness()
{
    typedef explode<std::vector<int>> exv;

    exv::template_<char> vchar;  // The second parameter still has its default argument!
    exv::template_<exv::type_parameter<0>, exv::type_parameter<1>> vint;

    static_assert(std::is_same<exv::template_<char>, std::vector<char>>::value, "");
    static_assert(std::is_same<decltype(vchar), std::vector<char>>::value, "");
    static_assert(std::is_same<decltype(vint), std::vector<int>>::value, "");
    static_assert(std::is_same<exv::type, std::vector<int>>::value, "");
    static_assert(std::is_same<exv::type_parameter<0>, int>::value, "");
    static_assert(std::is_same<exv::type_parameter<1>, std::allocator<int>>::value, "");
}
#endif

但对于非类型参数,我还没弄清楚它是否可能。您可以从类似的代码开始

template <class... ArgTypes, template <ArgTypes... Args> class T, ArgTypes... N>
struct explode<T<N...>>
{
    typedef T<N...> type;
    template <ArgTypes... Args> using template_ = T<Args...>;
    template <int I> using type_of_parameter =
        typename std::tuple_element<I, std::tuple<ArgTypes...>>::type;
    template <int I> struct nontype_parameter {
        static constexpr type_of_parameter<I> value() {
            return std::get<I>(std::tuple<ArgTypes...>(N...));
        }
    };
};

};

但是Clang(至少)不接受它:

test.cc:8:8: warning: class template partial specialization contains a template
      parameter that can not be deduced; this partial specialization will never
      be used
struct explode<T<N...>>
       ^~~~~~~~~~~~~~~~
test.cc:7:20: note: non-deducible template parameter 'ArgTypes'
template <class... ArgTypes, template <ArgTypes... Args> class T, ArgTypes... N>
                   ^

即使您以某种方式使问题消失,您仍然必须使用手动编码的std::get版本替换constexpr,因为标准库的std::get不是constexpr无论出于何种原因。

答案 1 :(得分:2)

答案可能来得有点晚,但可能会帮助其他人...

错过的尝试...

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


<块引用>

这个原始答案是作为我在 SO 上的第一个答案的纪念品。
有人会说,这不完全是失败。而是,第一次错过的尝试......;)
现在,跳到下一条水平线...

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

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

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

我做的第一个版本有 foo 的特化,派生自一个类似于 std::integral_constant 的类。让 foostd::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 功能,则应该首选上面发布的解决方案,因为它不仅管理整数类型

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

°序言

<块引用>
  • 经过我的研究,似乎这个特定问题的解决方案尚未找到(或未发布) 到现在。所以我想我应该详细介绍一下“为什么”“方法”我希望它会受到赞赏,但总的来说:有用...
  • 我目前正在编写一个包含编译时工具和功能的元编程库。希望能尽快在github上发布。
    (谁知道)——无论如何...
  • 当我意识到我的第一个答案是在 C++17 之后才正确时,我没有感到沮丧... – 不完全“准时”,可以说。 .. :P
  • 考虑到 ATM 的所有“仅编译时”功能机制,我觉得 9 年前应该有办法做到这一点。
  • 我开始考虑如何仅使用 C++11 特性来完成这项工作,大约一个小时后,我找到了一个可行的解决方案(实际上是两个)
  • 我花了更多时间使它成为可用的解决方案(实际上是两个)
  • 还有很多东西可以纠正这篇文章...:D

    毕竟,可能有编译器
    “足够好” 仅能理解C++11... :P

显然,由于当时可用的功能集更窄,
找到的解决方案只是有点更冗长... < em>:D

°搜索过程

首先,必须记住,当编译器输出无法推导出...
– 这并不意味着有错误(尽管可能有错误)
– 而是意味着编译器并不像人们想象的那么聪明。
– 这意味着一个人必须帮助编译器才能完成它的工作......

清楚吗?
– 编译器恳请您做它的一部分工作。
– 你很有机会:

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

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

因此,首先必须做一些事情来表示 typename Tx 的值(类型为 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 甚至不是作为特化参数传递的表达式的一部分。因此,问题不是将 typename 赋予参数 T,而是将 type 赋予参数 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 { 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

不出意外,它按预期工作...
那么......现在可能的类型是什么?
根据{{​​3}}上的标准:

<块引用>

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

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

对于我们的案例,问题要求整型
好吧,标准对整数类型有什么看法。
让我们看看 template parameters 以找出答案:

<块引用>

..., 如果 T 是类型 bool, char, char8_t (C++20 起), {{ 1}}、char16_tchar32_twchar_tshortintlong任何实现定义 strong> 扩展的整数类型,包括任何有符号无符号cv限定变体。

哎哟!

因此,由于有 9 种类型 - 如果排除 long long (仅来自 C++20) 并考虑大多数情况下实现定义的整数类型这些整数类型的别名——必须对以下内容进行特化:

  • 9 char8_t
  • 9 signed
  • 9 signed const
  • 9 signed volatile
  • 有 36 个专业。
  • 然后,为未签名的版本再添加 36 个?!
<块引用>

免责声明 [注意事项]

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

等等,等等,等等……

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

  • 非类型参数如何'read'/'interpreted'
  • signed const volatile 是否有意义?
  • 如果它的值是 volatile 的一部分,是不是 typename 以某种方式暗示了

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

——同样,没有constunsignedchar16_tchar32_t版本。
– 此外,如果你再仔细阅读标准关于 std::is_integral 的内容,你可能会看到一些没有得到应有关注的东西......

<块引用>

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

好吧,好吧,好吧……

– 这将做更多的工作比一开始例外... <强>:P
– 事实证明,最终,wchar_t 模板的仅 14 个特化就足以管理99% 的所有可能的整数类型...< /p>

好吧,好吧……我认为对于这么少量的代码来说,写的太多了。

请在下面找到解决方案, – 让后人在这里 – 希望它可能对某人有用(至少对于第二个例子中使用的有趣的“诡计”)

°个人评论

<块引用>

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


工作解决方案

解决方案 #1

<块引用>

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

Extract

解决方案#2

<块引用>

在这个解决方案中,我们利用 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 的强大功能来声明函数模板重载,它永远不会在任何地方定义...

decltype