【问题标题】:C++11 constexpr function's argument passed in template argumentC++11 constexpr 函数的参数传入模板参数
【发布时间】:2012-02-21 04:05:46
【问题描述】:

这曾经在几周前起作用:

template <typename T, T t>
T            tfunc()
{
    return t + 10;
}

template <typename T>
constexpr T       func(T t)
{
    return tfunc<T, t>();
}

int main()
{
    std::cout << func(10) << std::endl;
    return 0;
}

但现在g++ -std=c++0x 说:

main.cpp: In function ‘constexpr T func(T) [with T = int]’:
main.cpp:29:25:   instantiated from here
main.cpp:24:24: error: no matching function for call to ‘tfunc()’
main.cpp:24:24: note: candidate is:
main.cpp:16:14: note: template<class T, T t> T tfunc()
main.cpp:25:1: warning: control reaches end of non-void function [-Wreturn-type]

clang++ -std=c++11 表示模板的tfunc&lt;T, t&gt;() 参数因为无效而被忽略。

这是一个错误,还是一个修复?

PS:

g++ --version => g++ (GCC) 4.6.2 20120120 (prerelease)

clang++ --version => clang version 3.0 (tags/RELEASE_30/final) (3.0.1)

【问题讨论】:

  • FWIW,Clang 3.1 HEAD 也出现同样的错误。

标签: c++ gcc c++11 metaprogramming constexpr


【解决方案1】:

回顾一下这个问题:你有两个函数接受T 类型的参数。一个将其参数作为模板参数,另一个作为“普通”参数。 我将调用这两个函数funcTfuncN 而不是tfuncfunc。 您希望能够从funcN 致电funcT。将后者标记为constexpr 没有帮助。

任何标记为constexpr 的函数都必须是可编译的,就好像constexpr 不存在一样。 constexpr 函数有点精神分裂。它们仅在某些情况下才升级为完整的常量表达式。

不可能以简单的方式实现 funcN 以在运行时运行,因为它需要能够为 t所有 个可能值工作。这将要求编译器实例化tfunc 的许多实例,每个实例对应一个 t。但是,如果您愿意使用 T 的一小部分子集,则可以解决此问题。g++ 中的模板递归限制为 1024,因此您可以使用以下代码轻松处理 1024 个 T 值:

#include<iostream>
#include<functional>
#include<array>
using namespace std;

template <typename T, T t>
constexpr T funcT() {
        return t + 10;
}

template<typename T, T u>
constexpr T worker (T t) {
        return t==0 ? funcT<T,u>() : worker<T, u+1>(t-1);

}
template<>
constexpr int worker<int,1000> (int ) {
            return -1;
}


template <typename T>
constexpr T       funcN(T t)
{
        return t<1000 ? worker<T,0>(t) : -1;
}

int main()
{
    std::cout << funcN(10) << std::endl;
    array<int, funcN(10)> a; // to verify that funcN(10) returns a constant-expression
    return 0;
}

它使用一个函数worker,它将“普通”参数t递归地转换为一个模板参数u,然后用它来实例化和执行tfunc&lt;T,u&gt;

关键行是return funcT&lt;T,u&gt;() : worker&lt;T, u+1&gt;(t-1);

这有局限性。如果您想使用long 或其他整数类型,则必须添加另一个专业化。显然,此代码仅适用于 0 到 1000 之间的 t - 确切的上限可能取决于编译器。另一种选择可能是使用某种二进制搜索,对于每个 2 的幂使用不同的工作函数:

template<typename T, T u>
constexpr T worker4096 (T t) {
        return t>=4096 ? worker2048<T, u+4096>(t-4096) : worker2048<T, u>(t);

}

我认为这将解决模板递归限制,但它仍然需要大量的实例化,并且如果它可以工作的话,编译会非常变慢。

【讨论】:

  • 您可以通过在 main (c++17 起) 中声明 constexpr auto 或特定的 return type 来验证 constexpr 函数。基本上constexpr auto var = funcN(10)。如果它有效,它就是 constexpr。
【解决方案2】:

我觉得constexpr 在“运行时”上下文中也必须有效,而不仅仅是在编译时。将函数标记为 constexpr 鼓励编译器尝试在编译时对其进行评估,但该函数仍必须具有有效的运行时实现。

实际上,这意味着编译器不知道如何在运行时实现这个函数:

template <typename T>
constexpr T       func(T t)
{
    return tfunc<T, t>();
}

一种解决方法是更改​​构造函数,使其将其t 参数作为普通参数,而不是模板参数,并将构造函数标记为constexpr

template <typename T>
constexpr T       tfunc(T t)
{
    return t + 10;
}
template <typename T>
constexpr T       func(T t)
{
    return tfunc<T>(t);
}

“常量表达式”分为三个级别:

  1. 模板 int 参数,或(非 VLA)数组大小 //必须是常量表达式
  2. constexpr // 可能是一个常量表达式
  3. 非常量表达式

您无法真正将列表中较低的项目转换为该列表中较高的项目,但显然可以采用其他途径。

例如调用这个函数

constexpr int foo(int x) { return x+1; }

不一定是常量表达式。

// g++-4.6 used in these few lines. ideone doesn't like this code. I don't know why
int array[foo(3)]; // this is OK
int c = getchar();
int array[foo(c)]; // this will not compile (without VLAs)

所以constexpr 函数的返回值是一个常量表达式只有当所有参数和函数的实现都可以在编译时执行时完成。

【讨论】:

  • constexpr int i = something_that_MUST_be_a_constexpr;
  • @Xeo,它必须至少constexpr。而template&lt;int t&gt; struct X; X&lt;i&gt; x; 要求iconstexpr 还要多。
  • 也许我不应该使用第二个constexpr 作为constant expression 的简写... :) 我只是想对constexpr 这么说,有时是必须 是一个常量表达式 - 当一个变量被声明为这样时。 (作为对“constexpr//可能是常量表达式的东西”的回答。)
  • 这个例子正好说明了为什么我不明白为什么我的代码不起作用:foo(3)foo(c) 是 g++ 的两种完全不同的解释。 foo(3) 将在编译时评估,foo(c) 将生成代码以在运行时执行此操作。那么,为什么foo(3) 被评估为运行时函数?
  • @Xeo,我同意“可能”是一个错误的选择。我会进一步考虑如何编辑它,您可以随意编辑它。 constexpr 周围有一定的“人格分裂”。如果某些条件成立,constexpr 只会毕业成为完整的“常量表达式”。我相信你已经明白了,挑战是为外行总结一下。 [而且我不是专家,因此我在各种编译器上疯狂地测试所有这些 - 每个编译器的行为都有点不同:-)]
【解决方案3】:

参数t 不是常量表达式。因此错误。还应注意,它不能是常量表达式。

您可以将常量表达式作为参数传递,但在函数内部,保存值的对象(参数)不是常量表达式。

由于t 不是常量表达式,它不能用作模板参数:

return tfunc<T, t>(); //the second argument must be a constant expression

也许,你想要这样的东西:

template <typename T, T t>
T  tfunc()
{
    return t + 10;
}

template <typename T, T t>  //<---- t became template argument!
constexpr T  func()
{
    return tfunc<T, t>();
}

#define FUNC(a)  func<decltype(a),a>()

int main()
{
    std::cout << FUNC(10) << std::endl;
}

现在应该可以工作了:online demo

【讨论】:

  • 大声笑“我也知道你没有阅读我帖子中的第二段。”
  • @Nawaz 如果我想抱怨错误的第一部分,我不需要阅读正确的第二部分,对吗?
  • @Gravemind 因为 constexpr 函数是一个函数。这背后的功能仍然是一个正常的功能。这失败与constexpr 无关,只是与函数参数不能用作模板参数的正常规则有关。
  • @Nawaz,该示例适用于 g++-4.6。而且我认为这是正确的行为,并且那个ideone已经过时了。但我可能错了。
  • @Aaron constexpr 函数的参数绝不是常量表达式。每当您在常量表达式中调用 constexpr 函数时,都会完成函数调用替换。然后,return 语句中使用的任何参数名称都将替换为参数表达式。所以不使用参数,而是直接使用参数。
【解决方案4】:

看起来它应该给出一个错误 - 它无法知道您将一个常量值作为 t 传递给 func。

更一般地说,您不能将运行时值用作模板参数。模板本质上是一种编译时构造。

【讨论】:

  • 并且constexpr 函数可以在编译时进行评估。
  • @Xeo 我看不出您的评论与此答案的相关性。听起来,我说“我的手不能用来四处走动”。然后你说“你的手臂可以用来移动你的手。”
  • @Johannes: 好吧,因为constexpr 函数可以在编译时评估,它们的参数可能在编译时就知道了,因此它们可以被使用作为模板参数 - 如果我们可以指定我们允许编译时值作为参数。可悲的是,constexpr void f(constexpr int x); 不起作用。我知道此时您可以只说template&lt;int x&gt; constexpr void f();,但仍然......例如,模板参数不受转换的影响。
  • @Xeo 看来您应该将这些优秀的 cmets 作为 cmets 提出问题。
猜你喜欢
  • 2014-06-27
  • 1970-01-01
  • 2012-08-14
  • 1970-01-01
  • 1970-01-01
  • 2016-07-15
  • 2023-03-16
  • 2012-07-10
相关资源
最近更新 更多