【问题标题】:template instantiation with constexpr function failure带有 constexpr 函数失败的模板实例化
【发布时间】:2015-02-09 04:56:19
【问题描述】:

我有模板类C,它有一个非类型但引用模板参数到类型P

class P {
public:
  int x;
  int y;
};

template <const P &x>
class C {
public:
  const int &f() { return x.x; }
};

我声明了一个P类型的全局变量:

P p = {33,44};

我还声明了一个返回对p的引用的函数:

constexpr const P &h() { return p; }

然后尝试在下面使用这些:

C<p> o;    // line 1
C<h()> oo; // line 2

当然,我对第一个实例化没有问题,但第二个实例化。我的编译器抱怨:

error: non-type template argument does not refer to any declaration

为什么会这样?我无法在规范中找到反对它的论据。我不确定这是否与Calling constexpr in default template argument 中的问题完全相同,其中讨论的是关于 nested 实例化的实例化点。这更像是一个类型问题,但是哪一个呢?我的函数h() 返回一个对定义明确的类型(const P &amp;)的明确定义变量的引用。我预计会发生一些内联以给出正确的结果,但事实并非如此。你能告诉我为什么吗?

将函数声明为内联不会改变任何问题。

实验是用Apple LLVM version 6.0 (clang-600.0.56) (based on LLVM 3.5svn 完成的。我也试过g++-mp-4.8 (MacPorts gcc48 4.8.3_2) 4.8.3,报错为:

'h()' is not a valid template argument for type 'const P&' because it is not an object with external linkage

看起来我对h() 的调用(这是一个constexpr,所以编译时可计算)不被视为这样......


我忘了说如果我们尝试像这样的另一个参考,问题是一样的:

const P &pp = p;

然后

C<pp> oo;

这次第一个编译器说:

non-type template argument of reference type 'const P &' is not an object

第二个:

error: could not convert template argument 'pp' to 'const P &'

pp 不是对象? pp 不是 const P&amp; 类型?好吧,我可以按原样使用它...我知道它是一个参考,但与本机参考没有区别,或者?

【问题讨论】:

  • FWIW GCC 4.9.1 还说error: ‘h()’ is not a valid template argument for type ‘const P&amp;’ because it is not an object with external linkage
  • 谢谢,但h() 返回pp 有外部链接。这适用于C&lt;p&gt;,那么为什么不使用C&lt;h()&gt;
  • 我认为问题在于自从在 C++ 中引入 constexpr 以来,非类型模板参数还没有被审查过。见N3413
  • 谢谢。我不确定该文档(非常有趣!)是否能回答我的问题。如果我理解它涉及类型等价/平等(这是我关心的另一个问题,但不是这个)......
  • 您的代码使用clang HEAD 工作,请参阅我更新的答案中的实时示例,因此该提案不仅被接受,而且我们可以看到它也能正常工作。

标签: c++ templates c++11 language-lawyer constexpr


【解决方案1】:

此限制似乎受以下提案 Allow constant evaluation for all non-type template arguments 的约束,仍在尝试确定此提案的状态。它说:

指针、引用和指向的指针的句法限制 成员很尴尬,并阻止了合理的重构。例如:

template<int *p> struct A {};
int n;
A<&n> a; // ok

constexpr int *p() { return &n; }
A<p()> b; // error

并进一步说:

限制的历史原因很可能是 C++ 以前没有足够强的规范 指针、引用或指向成员类型的常量表达式。 然而,情况已不再如此。现状是一个 评估这样的模板参数需要实现,但是 如果结果不为空,则必须丢弃结果。

除上述之外,对具有链接的实体的限制是 导出模板的工件,可能在 移除了模板类型参数的链接限制。

它会删除带有此限制的注释的这一部分:

未命名的左值和没有链接的命名左值

整条注释如下:

临时的、未命名的左值和没有链接的命名左值是 不可接受的模板参数,当相应的 模板参数有引用类型。

更新

这个提案N4268的修订版是adopted into the working draft at Urbana,我们可以在最新的工作草案N4296中看到变化。新说明如下:

临时对象不是可接受的模板参数,当 对应的模板参数有引用类型

规范部分是14.3.2 (temp.arg.nontype) 段落 1 在这个提案中会说:

对于引用或指针类型的非类型模板参数, 常量表达式的值不应引用(或指针 类型,不得是)的地址:

  • 一个子对象 (1.8),
  • 一个临时对象 (12.2),
  • 字符串文字 (2.14.5),
  • typeid 表达式 (5.2.8) 的结果,或
  • 预定义的 func 变量 (8.4.1)。

我们可以在最新的标准草案N4296中找到这个新的措辞。

看起来这个更改实际上已经在clang HEAD 中实现了,请参阅您的代码工作live,使用-std=c++1z 标志。这意味着更改应该是 C++17 的一部分,假设后续更改不会逆转或改变它。

【讨论】:

  • N4268,链接论文的修订版,被厄巴纳采用。
  • @T.C.谢谢,自从我回答后我才重新上线,这有助于快速确定。
【解决方案2】:

如果是

C<h()> oo;

§14.3.2/4 开始:

[注意:临时、未命名的左值和没有链接的命名左值是不可接受的模板- 对应的模板参数具有引用类型时的参数。

(强调我的)

【讨论】:

  • 标准中的注释不是,只是作为脚注或示例。有一个专门针对此的问题。不管怎样,很好的发现。
  • 我在阅读该部分时错过了未命名的左值!!!!但是现在,为什么即使内联函数 h 也不会产生预期的结果?你不认为这里需要的一切都在编译器的手中(没有陷阱)吗?这可能就是我没有被未命名的左值唤醒的原因...
  • 对不起 Jeffrey,Shafik 的回答有据可查。 Columbo,当然这不是规范性的,但有注释来澄清规范性部分,因此任何人都可以理解之前的陈述是这样说的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多