【问题标题】:Constexpr member function returning a template parameter not considered constexpr when accessed through a reference通过引用访问时,返回模板参数的 constexpr 成员函数不被视为 constexpr
【发布时间】:2016-04-28 05:43:48
【问题描述】:

当我声明一个模板类时,我可以在 constexpr 成员函数中使用任何非类型模板参数,例如:

template <int Value>
struct Foo {
    constexpr int value() const { return Value; }
};

我可以稍后使用这个函数来初始化 constexpr 变量,如下所示:

template <int Value>
void use_value(Foo<Value> foo) {
    constexpr int baz{ foo.value() };
}

int main() {
    Foo<10> bar;
    use_value(bar);
}

我不明白的是,当我引用foo 时...

template <int Value>
void use_value(Foo<Value>& foo) {       // by ref
    constexpr int baz{ foo.value() };
}

int main() {
    Foo<10> bar;
    use_value(bar);
}

...clang 会告诉我foo.value() 不是常量表达式!

test.cpp:8:24: error: constexpr variable 'baz' must be initialized by a constant expression
    constexpr int baz{ foo.value() };
                     ~~^~~~~~~~~~~~~
test.cpp:13:5: note: in instantiation of function template specialization 'use_value<10>'
      requested here
    use_value(bar);

为什么会这样?是否与在第一个示例中编译器能够遵循 foo 的整个生命周期这一事实有关,而当通过引用获取时,程序的其他部分可能会改变实例,以某种方式影响 constexpr-ness foo.value() 的?尽管这里显然不是这种情况(鉴于模板专业化,无法更改Value 的值),但我不确定您是否确实可以通过篡改中间的实例来“破坏” constexpr 成员函数在某些情况下执行。

编辑

好的,因此引用对 constexpr 不利,因为它们具有运行时位置 - 并且 constexpr 值在生成的可执行文件中被硬编码。持续参考怎么样?这有效:

int main() {
    constexpr int foo2{5};
    int const& bar2{ foo2 };
}

但是对于Foo 类,更改对const 的引用将无济于事:

template <int Value>
void use_value(Foo<Value> const& foo) {    // const ref
    constexpr int baz = foo.value();
}

我得到相同的编译器错误。这里有什么区别?

【问题讨论】:

  • 如果Foo::value 应该返回Value,你应该做它然后在use_value 中使用这个静态函数(并完全丢弃参数 foo)。
  • 我同意,这只是一个愚蠢的例子来说明我的意思。它并没有试图在编程方面有意义。
  • 嗯,归根结底,引用只是一个语义稍有不同的指针。对于constexpr,您需要值...

标签: c++ templates c++11 pass-by-reference constexpr


【解决方案1】:

想想你为什么要做你正在做的事。为什么要首先通过引用传递 constexpr ?没有任何好处,实际上毫无意义。

此外,int const&amp; 不是对int 的常量引用,而是对const int 的引用。因此,当您正确编写像 int const&amp; bar2{ foo2 } 这样的代码时,它就可以工作了。实际上没有“常量引用”这样的东西(但作为旁注,有常量指针:int * const)。

总而言之,不要这样做Foo&lt;Value&gt;&amp; foo,因为没有理由这样做。 Foo&lt;Value&gt; foo 完全按照您的要求执行并在编译期间进行评估。

【讨论】:

    【解决方案2】:

    通过引用传递的东西不能是 constexpr。而且,要使函数实际用作 constexpr,函数中使用的所有参数和对象也必须是 constexpr。现在,由于引用(甚至是模板对象)永远不能是 constexpr,这意味着更好的选择是编译器抛出错误并让开发人员知道,而不是替代方案生成函数的非 constexpr 版本。

    这是因为引用意味着它必须具有运行时内存位置(请参阅 C++11 右值和左值以及右值引用)。如果某些东西符合 constexpr 的条件,则意味着它仅在编译时已知和使用,并且结果以与硬编码数字或字符串相同的方式烘焙到生成的可执行文件中。

    编辑:

    常量引用 (const&) 仍然是引用。 const& 充当类似于常规引用的参数限定符,并且仍然需要运行时内存位置 - const& 对象必须“引用”另一个对象。见What is a constant reference? (not a reference to a constant)

    在您的示例中, const& 取决于 constexpr ,这很好。如果 constexpr 依赖于 const& 它将不起作用。

    【讨论】:

    • 在这种情况下,通过引用传递的不是所谓的 constexpr 值,而是包含“计算”它的代码的 Foo 实例。我看不出这如何影响实际的函数调用,因为就像你说的那样,常量 10 应该“烘焙到可执行文件中”,实际上,在某种意义上,它是通过模板参数中的值传递的。
    • 另外,我可以不引用一个没有运行时内存位置的静态变量吗?这同样适用于任何 constexpr 变量,不是吗?
    • 查看修订版,是的,静态变量确实有一个运行时内存位置——但每个实例只有一个而不是一个。如果它们没有运行时内存位置,则永远无法更改静态变量。
    • 好的,我现在明白了。如何通过不断的参考来获取 Foo ?我至少可以用 constexpr 变量初始化 const 引用。正在编辑问题...
    • 如果您的意思是 'const Foo& foo' 那么这也不起作用 - 这仅意味着您不会在函数内部更改 foo。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多