Search code examples
c++gccclangc++20user-defined-literals

C++ user-defined string literal template weird issue (getting string literal length as compile-time constant)


I'm trying to define a user-defined string literal template.

Shouldn't that code snippet work? / Why doesn't it work?

template<char...>
constexpr
int operator ""_x () {
    return 0;
}

int x = "abc"_x;

The operator declaration is 'accepted' by the compiler as valid - C++ is so strict on the user-defined literals that any deviation from the spec'ed declarations is diagnosed as error. Therefore the acceptence of that declaration suggests to me that it is fine - as I would expect - but none of the compilers I've tried (GCC, Clang, MSVC - all trunks on godbolt.org) compile the next definition which uses the operator, of which Clang and MSVC can't find matching literal operator, and GCC gives an amazingly weird error (you can see it full at godbolt.org), of which the part:

(...)
<source>:7:9: error: type/value mismatch at argument 1 in template parameter list for 'template<char ...<anonymous> > constexpr int operator""_x()'
    7 | int x = "abc"_x;
      |         ^~~~~~~
<source>:7:9: note:   expected a constant of type 'char', got 'char'

(compiler speaking) I expected char, but I got char... Maaan... I don't know what to do now! - is the most confusing...

I'm using Clang 15 for this project, so if there is a compiler specific fix for this, I would be grateful for that one.

EDIT: Based on the first answer - I must add that this is a minimal snippet showing the issue and is not the target use-case of that function - it being a template is critical - to be more specific: I want to implement multi-character literal operator, but unfortunately someone defining the C++ spec decided to forbid the int16/int32 type to be accepted as user-defined literal argument, which are the underlying types of Clang's 'ab'/'abc'/'abcd', which I decided to solve by using the string literal ("") instead of character one (''), but for that to work I need to restrict the acceptable length of the source string literals at compile-time, which (as far as my knowledge goes) is only possible in C++ if the literal is passed as template parameter.


Solution

  • You don't need a template in that case.

    #include <string>
    
    constexpr int operator""_x (const char* str, std::size_t) {
      return 0;
    }
    
    int x = "abc"_x;
    

    Templates are used for converting literals other than string literals:

    template<char...>
    constexpr
    int operator ""_x () {
        return 0;
    }
    
    int x = 10_x;
    

    With the new information in the question, limiting the acceptable string literal length:

    #include <string>
     
    template<std::size_t N>
    struct StringLiteral {
      char s[N]{};
      constexpr StringLiteral(char const(&p)[N]) {
        // restrict the acceptable length with 4 chars + terminator
        static_assert(N <= 5);
        std::copy(p, p + N, s);
      }
    };
     
    template<StringLiteral L>
    constexpr int operator""_x() {
      // Use L.s
      return 0;
    }
     
    int x = "abc"_x;
    

    https://godbolt.org/z/nvj1GGjbM

    Or make the struct the string literal length independent:

    #include <string>
     
    template<std::size_t N>
    struct StringLiteral {
      char s[N]{};
      std::size_t n{};
      constexpr StringLiteral(char const(&p)[N]) : n(N) {
        std::copy(p, p + N, s);
      }
    };
     
    template<StringLiteral L>
    constexpr int operator""_x() {
      // restrict the acceptable length with 4 chars + terminator
      static_assert(L.n <= 5);
      // Use L.s
      return 0;
    }
    
    int x = "abc"_x;
    

    https://godbolt.org/z/hdP4aoefn