使用constexpr函数失败的模板实例化

Jea*_*nès 18 c++ templates language-lawyer constexpr c++11

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

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

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

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

P p = {33,44};
Run Code Online (Sandbox Code Playgroud)

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

constexpr const P &h() { return p; }
Run Code Online (Sandbox Code Playgroud)

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

C<p> o;    // line 1
C<h()> oo; // line 2
Run Code Online (Sandbox Code Playgroud)

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

error: non-type template argument does not refer to any declaration
Run Code Online (Sandbox Code Playgroud)

为什么会这样?我无法在常规中找到反对它的论据.我不确定它是否与在默认模板参数调用constexpr完全相同的问题,其中讨论是关于嵌套 instanciation的实例化点.这是一个类型问题,但哪一个?我的函数h()返回对定义良好的类型(const P &)的定义良好的变量的引用.我预计会有一些内联会给出正确的结果但事实并非如此.你能告诉我为什么吗?

将函数声明为内联不会改变问题.

实验用完成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
Run Code Online (Sandbox Code Playgroud)

看起来我的调用h()(这是一个constexpr编译时可编译的)并不像这样......


如果我们尝试使用另一个这样的引用,我忘了说问题是一样的:

const P &pp = p;
Run Code Online (Sandbox Code Playgroud)

然后

C<pp> oo;
Run Code Online (Sandbox Code Playgroud)

这次第一个编译器说:

non-type template argument of reference type 'const P &' is not an object
Run Code Online (Sandbox Code Playgroud)

第二个:

error: could not convert template argument 'pp' to 'const P &'
Run Code Online (Sandbox Code Playgroud)

pp不是一个对象?pp是不是类型const P&?好吧,我可以使用它,因为它...我知道它是一个参考,但与原生参考无法区分,或?

Sha*_*our 15

看起来此限制受以下建议的约束.允许对所有非类型模板参数进行持续评估,仍尝试确定此提议的状态.它说:

对指针,引用和成员指针的语法限制很尴尬,并阻止合理的重构.例如:

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

constexpr int *p() { return &n; }
A<p()> b; // error
Run Code Online (Sandbox Code Playgroud)

并进一步说:

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

除上述内容外,对具有链接的实体的限制是导出模板的工件,并且在删除模板类型参数的链接限制时可能已被删除.

并且它会删除此条款的这一部分:

未命名的左值,以及没有链接的命名左值

整个笔记如下:

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

更新

N4268提案的修订版被纳入了Urbana的工作草案,我们可以看到N4296最新工作草案的变化.新的说明如下:

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

规范部分是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看到你的代码工作现场,使用该-std=c++1z标志.这意味着更改应该是C++ 17的一部分,假设后续更改没有反转或改变它.


Sho*_*hoe 5

如果是

C<h()> oo;
Run Code Online (Sandbox Code Playgroud)

§14.3.2/ 4踢:

[注意:当相应的template-parameter具有引用类型时,临时值,命名的左值和没有链接的命名左值是不可接受的模板参数.

(强调我的)

  • 对不起Jeffrey,Shafik的回答非常详细.Columbo,确定它不是规范性的,但有说明是为了澄清规范部分,因此任何人都可以理解先前的陈述是这样说的. (3认同)