During my research for an answer for this question I found (I did not know that before) that gcc and clang allow char
arrays to be template arguments if they are declared static
. E.g. this code compiles with gcc and 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 also allows that. However, to make it work with MSVC, I have to declare the two strings in the global namespace. Then it works just as well.
So my question is: What does the standard say about this? Which compiler (if any) is right?
Update:
This issue has been fixed in VS 2019 version 16.4 (msvc v19.24): https://developercommunity.visualstudio.com/content/problem/341639/very-fragile-ice.html
This is a change from C++14 to C++17 that looks like MSVS hasn't caught up with. Previously in [temp.arg.nontype] a non type argument had to be
A template-argument for a non-type, non-template template-parameter shall be one of:
for a non-type template-parameter of integral or enumeration type, a converted constant expression ([expr.const]) of the type of the template-parameter; or
the name of a non-type template-parameter; or
a constant expression ([expr.const]) that designates the address of a complete object with static storage duration and external or internal linkage or a function with external or internal linkage, including function templates and function template-ids but excluding non-static class members, expressed (ignoring parentheses) as & id-expression, where the id-expression is the name of an object or function, except that the & may be omitted if the name refers to a function or array and shall be omitted if the corresponding template-parameter is a reference; or
a constant expression that evaluates to a null pointer value ([conv.ptr]); or
a constant expression that evaluates to a null member pointer value ([conv.mem]); or
a pointer to member expressed as described in [expr.unary.op]; or
a constant expression of type std::nullptr_t
.
emphasis mine
and because of bullet 3 you could not use a block scope variable as block scope variables have no linkage per [basic.link]/10
Names not covered by these rules have no linkage. Moreover, except as noted, a name declared at block scope has no linkage.
In C++17 this changes. [temp.arg.nontype] now has
A template-argument for a non-type template-parameter shall be a converted constant expression of the type of the template-parameter. For a non-type template-parameter of reference or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
This now allows you to use a block scope static variable