c++c++20user-defined-literals

understanding user defined string literals addition for c++20


I found in user defined string literal the following:

  1. For user-defined string literals, let str be the literal without ud-suffix:

a) If the overload set includes a string literal operator template with a non-type template parameter for which str is a well-formed template argument, then the user-defined literal expression is treated as a function call operator "" X<str>(),

That sounds a bit mysterious to me. Can some one give an example how this can be used?

The following did not work at all and I can't catch the point what the non type template parameter for MyType can be. It seems not a char* nor const char*:

template < ??? >
struct MyType 
{
    const char* c;
    constexpr MyType( const char* in ): c{in}{}
};

template < MyType t > auto operator ""_y() { return t; }

int main()
{
    "Check it"_y;
}

Solution

  • This is confusing wording, which was copied directly from the standard:

    If [the overload set] contains a literal operator template with a non-type template parameter for which str is a well-formed template-argument

    The confusing bit is the question of what "for which str is a well-formed template argument" specifically applies to. A direct reading of the passage from the standard suggests that "for which" refers to the "non-type template parameter", since that is the text directly preceding the words "for which". However, if you look at how the standard says the function will be invoked, you see this:

    operator "" X<str>()
    

    str is being passed to the operator, which the implication being that an implicit conversion will take place between str and the "non-type template parameter". That is, str is a valid "template argument" of the overloaded function, not of the template parameter of the overloaded function. And thus, the "for which" part should refer to the "literal operator template with a non-type template parameter", not the "non-type template parameter".

    That having been said, to make your code work, you need to do more than to just remove the template argument from MyType.

    You might have noticed a certain oddity in C++ surrounding non-type template parameters (NTTP). For example, NTTPs have always been able to be pointers to things. But you could never do this:

    template<const char *literal> void foo() {}
    foo<"literal">();
    

    The standard expressly forbids a pointer NTTP from being initialized with a string literal. And C++20 does not change this.

    Therefore, you can't take a pointer. You have to take what the literal actually is: an array. But you can't make your code work by taking const char (&in)[] as a parameter either. A literal is not an unsized array (since an "unsized array" is not a real object type). That array parameter must be sized appropriately to the literal.

    Which means that you must deduce the size from a size template parameter.

    Also, other rules flat-out forbid you from ever storing a pointer to a string literal in an NTTP (directly or indirectly). So, if you want a type that represents an entire string literal in an NTTP, that NTTP type must contain an array that is sized to that size.

    So the simplest, functional string literal NTTP you could build would be:

    template<size_t N>
    struct string_literal
    {
        std::array<char, N> arr_;
    
        constexpr string_literal(const char(&in)[N]) : arr_{}   
        {
            std::copy(in, in + N, arr_.begin());
        }
    };
    

    And thanks to CTAD, you can just use template < string_literal t > auto operator ""_y() to define your UDL.

    Note that this string_literal class explicitly includes the NUL terminator as part of the array.