Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

String literal in templates - different behavior of compilers

Suppose we have the following code:

template <typename T>
void foo(const T&);

int main()
{
   foo("str");
}

Demonstration

gcc 4.7.2, clang 3.2, icc 13.0.1

undefined reference to `void foo<char [4]>(char const (&) [4])'

MSVC-11.0

unresolved external symbol "void __cdecl foo<char const [4]>(char const (&)[4])" (??$foo@$$BY03$$CBD@@YAXAAY03$$CBD@Z)

Notice char[4] in the first output and char const[4] in the second output.

Why? And who's right? Can you quote the standard, please?

like image 338
FrozenHeart Avatar asked Mar 19 '13 13:03

FrozenHeart


People also ask

How does compiler process string literal?

The compiler scans the source code file, looks for, and stores all occurrences of string literals. It can use a mechanism such as a lookup table to do this. It then runs through the list and assigns the same address to all identical string literals.

What is a string literal in programming?

A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string. You must always prefix wide-string literals with the letter L.

What is the type of a string literal in C++?

In C the type of a string literal is a char[]. In C++, an ordinary string literal has type 'array of n const char'. For example, The type of the string literal "Hello" is "array of 6 const char".

Are string literals static?

String literals have static storage duration, and thus exist in memory for the life of the program. String literals can be used to initialize character arrays.


1 Answers

GCC is right.

Let's start from a slightly simpler example, and later prove that the original example follows the same pattern:

template<typename T>
void bar(T const&)
{
    // Shall not fire
    static_assert(std::is_same<T, int>::value, "Error!");
}

int main()
{
    int x = 0;
    bar(x); // 1 - Assertion won't fire

    int const y = 0;
    bar(y); // 2 - Assertion won't fire
}

What's happening here? First of all, per § 14.8.2.1/3:

[...] If P is a reference type, the type referred to by P is used for type deduction. [...]

This means that type deduction will try to match T const against int (in case 1) and against int const (in case 2). In the second case, substituting int for T will yield a perfect match, so that's easy; in the first case, we have the const getting on our way to have a perfect match. But this is where § 14.8.2.1/4 comes into play:

[...] If the original P is a reference type, the deduced A (i.e., the type referred to by the reference) can be more cv-qualified than the transformed A. [...]

Here, replacing int for T gives us a deduced int const, which is more cv-qualified than int (the type of the argument x). But that is acceptable because of § 14.8.2.1/4 above, so even in this case T is deduced to be int.

Let's now tackle your original example (just slightly adjusted, but we'll get to the original version eventually):

template<typename T>
void bar(T const&)
{
    // Does not fire in GCC, fires in VC11. Who's right?
    static_assert(std::is_same<T, char[4]>::value, "Error!");
}

int main()
{
    char x[] = "foo";
    bar(x);

    char const y[] = "foo";
    bar(y);
}

Apart from the fact that I replaced int with char [], this is example and my first example are identical in structure. To see why this equivalence holds, consider the assertion below (which doesn't fire on any compiler, as expected):

// Does not fire
static_assert(
    std::is_same<
        std::add_const<char [4]>::type,
        char const[4]
    >::value, "Error");

The C++11 Standard mandates this behavior in Paragraph 3.9.3/2:

Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4).

Paragraph 8.3.4/1 also specifies:

[...] Any type of the form “cv-qualifier-seq array of N T” is adjusted to “array of N cv-qualifier-seq T”, and similarly for “array of unknown bound of T”. The optional attribute-specifier-seq appertains to the array. [ Example:

typedef int A[5], AA[2][3];
typedef const A CA; // type is “array of 5 const int”
typedef const AA CAA; // type is “array of 2 array of 3 const int”

—end example ] [ Note: An “array of N cv-qualifier-seq T” has cv-qualified type; see 3.9.3. —end note ]

Since it is now clear that the two examples exhibit the same pattern, it makes sense to apply the same logic. And that will lead us through the very same reasoning path.

While performing type deduction, T const is matched against char[4] in the first case and against char const[4] in the second case.

In the second case, T = char[4] yields a perfect match, because T const becomes char const[4] after the substitution. In the first case, the deduced A is once again more cv-qualified than the original A, in that substituting char[4] for T yields char const[4]. But then again, that is allowed by 14.8.2.1/4, so T should be deduced as char[4].

Finally, back to your original example. Since the string literal "str" also has type char const[4], T should be deduced to be char [4], which means that GCC is right:

template<typename T>
void foo(T const&)
{
    // Shall not fire
    static_assert(std::is_same<T, char[4]>::value, "Error!");
}

int main()
{
    foo("str"); // Shall not trigger the assertion
}
like image 58
Andy Prowl Avatar answered Sep 27 '22 16:09

Andy Prowl