Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Some const char * are unavailable at compile time?

Let's suppose we have a template function with non-type parameter of const char * like this:

template <const char * MESSAGE> void print() {
    std::cout << MESSAGE << '\n';
}

Using this template wouldn't be a problem as log as the MESSAGE can be deduced at compile-time, so the following uses are legal:

namespace {
    char namespace_message[] = "Anonymous Namespace Message";
    constexpr char namespace_constexpr_message[] = "Anonymous Namespace Constexpr Message";
}

char message[] = "Message";
constexpr char constexpr_message[] = "Constexpr Message";

int main()
{
    print<namespace_message>();
    print<namespace_constexpr_message>();

    print<message>();
    print<constexpr_message>();

    return 0;
}

But the ones below are not (see here):

namespace {
const char namespace_const_message[] = "Anonymous Namespace Const Message";
}

const char const_message[] = "Const Message";

int main()
{
    print<namespace_const_message>();
    print<const_message>();
    print<"Literal">();

    return 0;
}

The errors generated by the code above are the following:

the value of '{anonymous}::namespace_const_message' is not usable in a constant expression

I don't get why namespace_const_message is not usable in a constant expression while namespace_message is; if I must bet for one of them to be unable to be used in a constant expression I'll bet for the no constant one, but is the one which already works as constant expression!

note: '{anonymous}::namespace_const_message' was not declared 'constexpr'

namespace_message was neither declared as constexpr and is used into a constant expression and its value is deduced at compile time. Why constexpr is needed if the expression is const and not required if no-const?

Same goes for the values outside the anonymous namespace, I was trying to force the compile-time-constness placing the values into a internal linkage space but is obvious that I've failed.

Finally, the last error:

'"Literal"' is not a valid template argument for type 'const char*' because string literals can never be used in this context

So, surprisingly (at least it was a surprise for me) a string literal cannot be used as template argument, but as long as the string (well, a pointer to a null-terminated array of characters) is a compile-time value it can be used as non-type template parameters so: they're available at compile-time as long as "they are a lvalue" (but they're already lvalues!).

I'm trying to guess why a string literal can never be used in this context, and my best guess is that two string literals with the same content aren't the same literal (because the pointer which points to the content could be different) while two integral literals are the same (they're a value, not a pointer to a value).

So, what's the question here?

  • Why the namespace_const_message and const_message aren't available at compile-time and thus forbidden in the print template function?
  • Is my guess about the string literals correct?

Thanks.

like image 641
PaperBirdMaster Avatar asked Mar 02 '15 12:03

PaperBirdMaster


3 Answers

The instantiation variable of a template needed to have external linkage, and const was implicitly internal linkage. So you have to write:

extern char const constMessage[] = "Const message";

(Another alternative would be for it to be a static class member. Static class members always have external linkage.)

The case of string literals is in some ways similar: their type is char const[]. But it's even worse: template instantiations (at least the early ones) need a name, and a string literal doesn't have one. Even more to the point, it's unspecified whether identical string literals are the same object or not, so in the following:

template <char const* m>
struct Toto { char const* f() const; };

Toto <"titi"> t1;
Toto <"titi"> t2;

it would be unspecified whether t1 and t2 had the same type or not.

like image 189
James Kanze Avatar answered Nov 12 '22 02:11

James Kanze


From the c++11 standard §14.3.2.1

Template non-type arguments

A template-argument for a non-type, non-template template-parameter shall be one of:

  1. for a non-type template-parameter of integral or enumeration type, a converted constant expression (5.19) of the type of the template-parameter; or
  2. the name of a non-type template-parameter; or
  3. a constant expression (5.19) that designates the address of an 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, 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
  4. a constant expression that evaluates to a null pointer value (4.10); or
  5. a constant expression that evaluates to a null member pointer value (4.11); or
  6. a pointer to member expressed as described in 5.3.1; or
  7. an address constant expression of type std::nullptr_t.

To your questions:

Why the namespace_const_message and const_message aren't available at compile-time and thus forbidden in the print template function?

That's why constexpr exists. They can be used where it's needed compile-time evaluation, thus available to be template-arguments.

Is my guess about the string literals correct?

There is a note about this right after the arguments:

Note: A string literal (2.14.5) does not satisfy the requirements of any of these categories and thus is not an acceptable template-argument.

like image 4
hlscalon Avatar answered Nov 12 '22 03:11

hlscalon


You can also use a std::array<char, N>. The sample below requires C++20:

#include <array>   // std::array
#include <cstddef> // std::size_t

template <auto constexpr_string>
void needs_constexpr_string() {
    // ... use the string ...
}

template <auto N>
consteval auto str(char const (&cstr)[N]) {
    std::array<char, N> arr;
    for (std::size_t i = 0; i < N; ++i)
        arr[i] = cstr[i];
    return arr;
}

int main() {
    needs_constexpr_string<str("Hello World")>();
}
like image 1
Ayxan Haqverdili Avatar answered Nov 12 '22 02:11

Ayxan Haqverdili