为什么这不是一个常量表达式?

29
在这个简单的例子中,即使 test1 成功通过编译,但是 test2 无法通过编译,我不知道为什么会出现这种情况。如果 arr[i] 适合作为一个被标记为 constexpr 的函数的返回值,那么它为什么不能作为非类型模板参数呢?
template<char c>
struct t
{ 
    static const char value = c;
};

template <unsigned N>
constexpr char test1(const char (&arr)[N], unsigned i)
{
    return arr[i];
}

template <unsigned N>
constexpr char test2(const char (&arr)[N], unsigned i)
{
    return t<arr[i]>::value;
}

int main()
{
    char a = test1("Test", 0); //Compiles OK
    char b = test2("Test", 0); //error: non-type template argument 
                               //is not a constant expression
}

编辑:这没有任何影响:

template<char c>
struct t
{ 
    static const char value = c;
};

template <unsigned N>
constexpr char test1(const char (&arr)[N])
{
    return arr[0];
}

template <unsigned N>
constexpr char test2(const char (&arr)[N])
{
    return t<arr[0]>::value;
}

int main()
{
    char a = test1("Test"); //Compiles OK
    char b = test2("Test"); //error: non-type template argument 
                            //is not a constant expression
}
5个回答

30

简短回答:在中不存在constexpr函数参数。

详细回答:在test1()中,如果i不是编译时常量,则该函数仍可在运行时使用。但在test2()中,编译器无法确定i是否为编译时常量,而函数却需要它来编译。

例如,以下test1代码将编译:

int i = 0;    
char a = test1("Test", i); // OK, runtime invocation of test1()

constexpr int i = 0;
constexpr char a = test1("Test", i); // also OK, compile time invocation of test1()

让我们简化你的test2()

constexpr char test3(unsigned i)
{
    return t<i>::value;
}

test3(0)无法编译,因为在test3()内部,不能证明i是一个无条件的编译时表达式。你需要使用constexpr函数参数才能表达这一点。

标准引用

5.19 常量表达式 [expr.const]

2 条件表达式 e 是核心常量表达式,除非按照抽象机器的规则(1.9)对 e 的评估将评估以下表达式之一:

— 引用类型的变量或数据成员的 id 表达式,除非该引用具有前置初始化并且
— 它用常量表达式初始化或

— 它是对象的非静态数据成员,在 e 的评估期间该对象的生存期已经开始;

本节有以下代码示例与您的问题相对应:

constexpr int f1(int k) {
    constexpr int x = k; // error: x is not initialized by a
                         // constant expression because lifetime of k
                         // began outside the initializer of x
    return x;
}

在上面的例子中,由于x不是常量表达式,这意味着您无法在f1内实例化带有xk的模板。


那不是这样的。如果你从test2中删除unsigned i,并改为使用arr[0]作为索引,你仍然会得到相同的编译错误。 - Chris_F
如果您在test2中尝试t<test1(arr,i)>::value,请查看编译器的输出。显然,在这种情况下test1不是constexpr,因为arri通常是运行时参数。该函数必须编译无论它们是字面量还是其他类型。 - Useless
“constexpr函数参数”是否可能被添加到语言中? - Chris_F
除非有人撰写提案,否则不会实现。我想在std-proposals新闻组中已经有过建议。但是,例如constexpr的lambda表达式是我更希望实现的东西。 - TemplateRex

8

这里存在一个对于constexpr的误解。它表示一个函数必须在编译时对于合适的参数可求值,但并不意味着在一般情况下不需要进行编译。

我们来看一下第一个版本:

template <unsigned N>
constexpr char test1(const char (&arr)[N], unsigned i) {
    return arr[i];
}

现在,这显然是编译时评估:
enum { CompileTimeConstant = test1("Test", 0) };

你的例子可能是优化器/质量问题:

char MayBeCompileTimeConstant = test1("Test", 0);

这个例子显然不是,但仍然需要被评估

char arr[10];
int i;
std::cin >> i;
std::cin >> arr;
char b = test1(arr, i);
std::cout << "'" << arr << "'[" << i << "] = " << b << '\n';

因为在最后一种情况下,test2 无法编译,所以它根本无法编译。(请注意,我并不是在暗示该代码是好的。)


3
这里的问题在于调用 arr[i] 会引发下标运算符 operator[]该运算符不会返回一个常量表达式。 实际上,这不是 constexpr 的问题,而是模板参数推导的问题。非类型模板参数必须是常量表达式,而下标运算符的返回值并不是常量表达式。
因此,编译器完全有理由抱怨 arr[i] 不是常量表达式。

1
因为 arr[i] 不是编译时常量表达式。它在运行时可能会有所不同。

那不是这样的。如果你从test2中删除无符号i,而是将其索引为arr [0],你仍然会得到相同的编译错误。 - Chris_F
@Chris_F arr[i](甚至是arr[0])即使是const char[],也不是编译时常量。 - user1742529
@Chris_F 错误:常量表达式中不能出现数组引用 - user1742529

0

你可以通过动态数组大小调整来解决这个问题。你可以使用vector类来实现,因为在vector类中,你可以动态地调整列表的大小。或者你可以使用malloc,但是这将浪费很多时间,而vector类已经存在了。我只是说一下。


你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接