标准对于字符数组作为模板参数有什么规定?

12

在回答这个问题的研究中,我发现(之前不知道)gcc和clang允许char数组作为模板参数,如果它们被声明为static。例如,这段代码可以在gcc和clang中编译:

#include <type_traits>

template <int N, const char (&string)[N]>
auto foo()
{
    if constexpr (string[0] == 'i')
        return 0;
    else
        return 3.14f;
}

void bar()
{
    static constexpr char string1[] = "int";
    static constexpr char string2[] = "float";

    auto i = foo<sizeof(string1), string1>();
    auto f = foo<sizeof(string2), string2>();

    static_assert(std::is_same_v<decltype(i), int>);
    static_assert(std::is_same_v<decltype(f), float>);
}

MSVC也允许这样做。但是,为了使其在MSVC中工作,我必须在全局命名空间中声明这两个字符串。然后它就能正常工作了。

所以我的问题是:标准对此有什么说法?哪个编译器(如果有的话)是正确的?


更新:

这个问题已经在VS 2019版本16.4(MSVC v19.24)中得到了修复:https://developercommunity.visualstudio.com/content/problem/341639/very-fragile-ice.html


问题重现. - Xirema
请注意,这仅适用于 字符串字面值,如果您创建一个实际的 char 数组作为模板参数,例如 static constexpr char p[] = {'i','n','t'},它将在 MSVC 下失败,如 此错误所述。 - stevecu
1个回答

12
这是从C++14到C++17的变化,看起来MSVS还没有跟上。以前在[temp.arg.nontype]中,非类型参数必须是:

对于非类型模板参数,它应该是以下之一:

  • 对于整数或枚举类型的非类型模板参数,它应该是一个转换后的常量表达式([expr.const]),其类型与模板参数的类型相同;或者

  • 非类型模板参数的名称;或者

  • 一个常量表达式([expr.const]),指定具有静态存储期和外部或内部链接的完整对象或函数的地址,包括函数模板和函数模板ID,但不包括非静态类成员,表示为& id-expression(忽略括号),其中id-expression是对象或函数的名称,但如果名称引用函数或数组,则可以省略&,如果相应的模板参数是引用,则必须省略&;或者

  • 一个求值为null指针值([conv.ptr])的常量表达式;或者

  • 一个求值为null成员指针值([conv.mem])的常量表达式;或者

  • 按[expr.unary.op]中所述的方式表示的成员指针;或者

  • 类型为std::nullptr_t的常量表达式。

我加了重点

由于第三点规则的限制,您无法将块作用域变量用作它们没有链接[basic.link]/10

未被这些规则覆盖的名称没有链接。此外,除非另有说明,声明为块作用域的名称没有链接。

在C ++17中,这种情况发生了变化。[temp.arg.nontype]现在具有

非类型模板参数的模板参数必须是转换常量表达式,其类型为模板参数的类型。对于引用或指针类型的非类型模板参数,常量表达式的值不得引用(或对于指针类型,不得是):

  • 子对象,

  • 临时对象,

  • 字符串字面量,

  • typeid表达式的结果,或

  • 预定义的­­func_­_­变量。

现在,这使您可以使用块作用域静态变量。

1
如何向MSVC开发人员报告此类问题? - j00hi
@j00hi 如果我没记错的话,你可以直接通过MSVS提交错误报告。 - NathanOliver
1
一个块作用域的静态变量应该是非类型模板参数的有效模板参数,但在MSVC中却不是。 (欢迎对措辞/短语/问题描述提出建议!) - j00hi

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