Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constexpr cast to const char[]

It has happened to many people, and it happened to me. I got stuck playing with compile time strings in C++.

I decided to take the apparently unusable approach: using template <char...> classes.

This is what I came up with, it is very common, nothing special, and also it does not work.

template <char... chars> class string
{
public:

    static constexpr const char value[] = {chars...};

    constexpr string()
    {
    }

    constexpr operator decltype(value) & () const
    {
        return value;
    }
};

template <char... chars> constexpr const char string <chars...> :: value[];

My idea was making a string instance constexpr constructible and exposing some kind of constexpr casting so that it would provide its content.

Now, if I do

static constexpr const char x[] = "ciao";

template <const char * str> void print()
{
    std :: cout << str << std :: endl;
}

print <x> ();

This works and says ciao. I get ciao also if I do

std :: cout << string <'c', 'i', 'a', 'o'> {} << std :: endl;

or

print <string <'c', 'i', 'a', 'o', '\0'> :: value> ();

But when I do

print <string <'c', 'i', 'a', 'o', '\0'> {}> ();

I get: No matching function for call to print.

I am definitely missing something. Is it unfeasible to do what I am trying to do? Making an instance do a constexpr cast to somehow return value? If that worked, I would be able to easily make operators and string manipulation at compile time, the "only" downside being the ultra-boring 'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n'.

Further experiments

I did another experiment that works perfectly.

template <char... chars> class string
{
public:
   constexpr string()
   {
   }

   constexpr operator size_t () const
   {
      return sizeof...(chars);
   }
};

template <size_t length> void print()
{
   std :: cout << length << std :: endl;
}

print <string <'c', 'i', 'a', 'o'> {}> ();

And it prints out a pretty 4.

like image 664
Matteo Monti Avatar asked Jul 03 '16 19:07

Matteo Monti


1 Answers

In C++14, the restriction on template arguments to a template non-type parameter is:

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 (5.19) of the type of the template-parameter; or
* the name of a non-type template-parameter; or
* a constant expression (5.19) 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 (4.10); or
* a constant expression that evaluates to a null member pointer value (4.11); or
* a pointer to member expressed as described in 5.3.1; or
* a constant expression of type std::nullptr_t.

In your example, string<'c', 'i', 'a', 'o', '\0'>{} is none of those. Note that the first bullet is restricted to integral or enumeration type, and const char* is neither of those (the exception for integral types is what allows your "Further experiments" example to compile). None of the other bullets apply. So a conforming C++14 should reject your code.

The workaround is to simply pass the underlying character array directly:

print<string<'c', 'i', 'a', 'o', '\0'>::value>();

Or to take the type itself as the template type argument, and print ::value within the function:

template <class T>
void print() {
    std::cout << T::value << std::endl;
}

print<string<'c', 'i', 'a', 'o', '\0'>>();

Or...


You'll be happy to know that in C++17, things get better. See N4198, with the motivating example:

template<int *p> struct A {};
int n;
A<&n> a; // ok

constexpr int *p() { return &n; }
A<p()> b; // error

The paper proposed fewer restrictions on template non-type arguments, which would make the above example well-formed. The new wording reads:

A template-argument for a non-type template-parameter shall be a converted constant expression (5.20) of the type of the template-parameter. For a non-type template-parameter of reference type or pointer type, the value of the constant expression shall not refer to (or for a pointer type, shall not be the address of):
* a subobject (1.8),
* a temporary object (12.2),
* a string literal (2.14.5),
* the result of a typeid expression (5.2.8), or * a predefined __func__ variable (8.4.1).

That's it. Everything else is allowed.

Since string<'c', 'i', 'a', 'o', '\0'>{} converts to a pointer that is none of those things, the example becomes well-formed. Clang 3.8 will compile your example in c++1z mode (correctly) but not in c++14 mode (correctly). GCC does not implement this yet. Just give them time.

like image 127
Barry Avatar answered Nov 15 '22 12:11

Barry