C++17 String Literals – How to Define User-Defined String Literal Operator Templates

c++c++17clangg++string-literals

According to my (draft) version of C++17 (16.5.8 [over.literal]) as well as as cppreference.com, C++17 should have support for templated operators for user-defined string literals.

Specifically:

template <char...>
double operator "" _pi() {
    return 0.;
}

int main() {
  "test"_pi;
}

However, both gcc and clang yell at me:

// gcc -Wall -Wextra -pedantic -std=c++17
error: no matching function for call to 'operator""_pi<char, 't', 'e', 's', 't'>()'
    7 |   "test"_pi;
      |   ^~~~~~~~~
note: candidate: 'template<char ...<anonymous> > double operator""_pi()'
    2 | double operator "" _pi() {

// clang -Wall -Wextra -pedantic -std=c++17
error: no matching literal operator for call to 'operator""_pi' with arguments of types 'const char *' and 'unsigned long', and no matching literal operator template

(live demo)

They both seem to want to nudge me towards the following template, which used be briefly considered to be part of C++17, but to my understanding did not become part of it:

template <typename T, T...>
double operator "" _pi() {
    return 0.;
}

However, both gcc and clang correctly warn that this is a non-standard extension but they compile it correctly. Both gcc and clang claim to have full support of the C++17 core language.

What am I doing wrong? Is my draft AND cppreference.com inaccurate? I currently don't have access to the final standard version.


Use case

My use case is to implement the following, which is why I need the compile time information.

template <char... cs>
constexpr auto operator "" _a() {
  return std::array<char, sizeof...(cs)>({cs...});
}
// "Hello"_a creates a std::array<char,5>

Best Answer

The template char... user defined literal is not for strings. It is called a numeric literal operator template and it allows you to have a literal operator that will take the number you are using and expand it into a pack for the template operator. For example, you would use your operator like

1234_pi

and that would resolve to calling

operator "" <'1', '2', '3', '4'> _pi()

What you'd like to have is not possible in stock C++17

To get this to work, you'll need to upgrade to C++20, which lets you use a user defined type that can be constructed from a string literal, and use that to build the std::array. This is what is now called a string literal operator template. That would look like

template<std::size_t N>
struct MakeArray
{
    std::array<char, N> data;
    
    template <std::size_t... Is>
    constexpr MakeArray(const char (&arr)[N], std::integer_sequence<std::size_t, Is...>) : data{arr[Is]...} {}
 
    constexpr MakeArray(char const(&arr)[N]) : MakeArray(arr, std::make_integer_sequence<std::size_t, N>())
    {}
};
 
template<MakeArray A>
constexpr auto operator"" _foo()
{
    return A.data;
}

and then

"test"_foo;

and that will resolve to a std::array<char, 5>. If you don't want the null terminator in the array, You just need to subtract 1 from N everywhere it is used except for the char const(&arr)[N] parts.

If you can't use C++20, you could switch this over to a couple of functions like

template <std::size_t N, std::size_t... Is>
constexpr auto as_array_helper(const char (&arr)[N], std::integer_sequence<std::size_t, Is...>)
{
    return std::array<char, N>{arr[Is]...};
}

template <std::size_t N>
constexpr auto as_array(const char (&arr)[N])
{
    return as_array_helper(arr, std::make_integer_sequence<std::size_t, N>());
}

and then you would use it like

as_array("test")    
Related Question