【问题标题】:Why is initialization of a constant dependent type in a template parameter list disallowed by the standard?为什么标准不允许在模板参数列表中初始化常量依赖类型?
【发布时间】:2015-11-13 07:22:27
【问题描述】:

在对这篇帖子“(Partially) specializing a non-type template parameter of dependent type”的回答中,它指出:

模板参数的类型对应于一个专门的 非类型参数不应依赖于 专业化。 [ 例子:

template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error

template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error

——结束示例]

我的问题是为什么这里有这个限制?至少在一个用例中,我发现此限制会干扰编写干净的代码。例如

template <typename T, T*>
struct test;

template <typename T>
struct test<T, nullptr> // or struct test<T, (T*)nullptr>
{
};

template <typename R, typename...ARGs, R(*fn)(ARGs...)>
struct test<R(ARGs...), fn>
{
};

虽然我不确定是否还有其他情况表明基于类型声明常量是一个没有任何意义的问题。

有人知道为什么会这样吗?

【问题讨论】:

  • 我的意思是我认为如果你有一堆类型有例如static const int value = 42; 和其他一些具有static const char value = 'a' 的类型,那么您可能可以部分专门化基于&lt;typename T, T::v&gt; 的模板...我想不出任何特定的限制原因,我不认为允许那种替换和模式匹配会比他们已经做的更难。但也许我错过了一些东西。
  • FWIW 可能没有真正的答案。该标准还说“您不应将指针指向构造函数”,并且 afaik 没有特别令人信服的理由来限制这种限制
  • @ChrisBeck 这更合理和可以理解。构造函数的对象需要内存,因此在调用之前需要隐藏代码。这种分配可以直接在调用之前,也可以在编译器定义的任何其他地方之前。调用构造函数而不说明对象的存储位置是没有意义的,因此指向构造函数的指针不仅没有意义,而且非常危险。 Inplace new 允许程序员指定内存位置,前提是这种细粒度使用实际上是有保证的,并且可以生成工厂函数来代替构造函数。
  • @ChrisBeck 这将是您的函数:template &lt;typename CLASS, typename...ARGs&gt; CLASS factory(ARGs&amp;&amp;...args) { return CLASS(std::forward&lt;ARGs...&gt;(args...)); } 您的函数指针将是 auto pConstrutor = &amp;factory&lt;class_name, arg0_t, ..., argN_t&gt;,而您的调用将是 class_name var = pConstructor(arg0, ..., argN)。这将是我在这个 OT 线程中的最后一篇文章
  • @T.C. : 从 N0668 的最后一页开始,他们给出了一个算法来确定哪些类的部分模板特化比其他的更具体,它基本上将非类型参数分开处理,并将早期的函数模板算法用于类型参数。我猜如果非类型参数的类型取决于类型参数,这将不起作用。所以我的假设是,标准委员会只是觉得它为编译器编写者节省了大量工作来禁止这样做?这听起来合理吗?

标签: c++ templates c++11 c++14 template-meta-programming


【解决方案1】:

(恕我直言)标准不允许特定功能的最常见原因是:

  1. 该功能被语言中的另一种机制所覆盖,因此显得多余。
  2. 它与现有的语言逻辑和实现相矛盾,使其实现潜在的代码破坏。
  3. 遗留:该功能一开始就被遗漏了,现在我们已经构建了很多没有它的东西,几乎被遗忘了(请参阅部分功能模板专业化)。

实现困难很少是一个因素,尽管编译器实现可能需要一些时间才能赶上“硬”东西的发展。

您总是可以将您的非类型模板参数包装在另一种类型中:

template < typename T1, typename T2 > 
struct Demo {}; // primary template

template < typename T > 
struct Demo<T, integral_constant<T, 0>> {}; // specialization

我怀疑this hack 属于案例 1。案例 3 总是有可能,所以让我们检查案例 2。为此,我们必须知道标准对类模板部分专业化施加的相关规则。

14.5.5 类模板部分特化

  1. 如果非类型参数是非类型参数的名称,则它是非特化的。所有其他非类型参数都是专门的。 (C1)

  2. 在类模板部分特化的参数列表中,适用以下限制:

    • 部分特化的非类型实参表达式不应涉及部分特化的模板形参,除非实参表达式是简单标识符。 (C2)
    • 对应于专门化的非类型实参的模板形参的类型不应依赖于专门化的参数。 (C3)

我标记了我认为相关的前三个 C 理由(第三个是有问题的)。根据我们案例中的 C1 我们有一个专门的非类型参数所以 C2 应该支持,但是这个

template <class T, T t> struct C {};
template <class T> struct C<T, 1>;

其实是

template <class T, T t> struct C {};
template <class T> struct C<T, T(1)>; // notice the value initialization

所以部分特化的非类型实参T t 涉及部分特化class T 的模板参数在一个不是标识符的表达式中;此外,此类专业化必然会使class T 参与值初始化,这将始终违反规则。然后 C3 出现并为我们清除了这一点,这样我们就不必每次都进行扣除。

到目前为止,我们已经确定规则与其自身同步,但这并不能证明情况 2(一旦我们删除了初始限制,所有其他相关限制都会崩溃)。我们必须深入研究匹配类模板部分特化规则;部分排序在这里被认为超出了范围,因为如果我们可以产生有效的候选者,则由程序员来组合一个格式良好的程序(即不要创建类模板的模棱两可的使用)。

部分

类模板偏特化的匹配[temp.class.spec.match]

描述了模板专业化中涉及的(给予或接受)“模式匹配”过程。规则1 是程序的整体工作流程,后续规则是定义正确性的规则

  1. 如果部分特化的模板参数可以从实际模板参数列表中推导出来,则部分特化匹配给定的实际模板参数列表

  2. 也可以从主模板的非类型参数的实际模板参数的值推导出非类型模板参数。

  3. 在引用类模板特化的类型名称中(例如 A),实参列表应与主模板的模板形参列表匹配。特化的模板参数是从主模板的参数推导出来的。

不违反这些规则,允许对应于专门化的非类型实参的模板形参的类型依赖于专门化的形参。所以恕我直言,没有具体原因为什么我们不能在未来的语言版本中拥有此功能:应该归咎于遗留问题。遗憾的是,我没有找到任何主动引入此功能的语言建议。

【讨论】:

  • furthermore such specializations are bound to involve class T` 在值初始化中总是违反规则` - 怎么会这样? constexpr 构造函数不会让这不是问题吗?
  • @Adrian 我看不出constexpr 与这里有什么关系。参数表达式仍然不是一个简单的标识符
  • 哦,是的,没错。我将您的陈述与我的示例混淆了,它是template &lt;typename T, T* p&gt;,因为它是指向类型而不是类型本身的指针。所以遗产是责备,没有理由这样做。嗯。
猜你喜欢
  • 2017-07-11
  • 1970-01-01
  • 2013-07-05
  • 2013-09-05
  • 2014-07-27
  • 1970-01-01
  • 2021-03-14
  • 1970-01-01
相关资源
最近更新 更多