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.