3

Consider the following code:

using char_type = /*implementation defined*/;

void foo(const char_type*);

int main()
{
    foo("Hello World!");
}

The string literal "Hello World!" is a const char* that, depending on the implementation, may not be convertible to const char_type*. I want my code to be portable between different implementations so I thought I could define a literal to convert one char after another (this type of conversion is guaranteed to work):

consteval const char_type* operator"" _s(const char*, size_t);

and then use it like this foo("Hello World!"_s). However, the only implementation I can think of uses new to allocate space and std::copy but that would be extremely slow. I want to make the conversion at compile time and luckily I can use c++20 and the consteval keyword to ensure that the call to the function always produces a contant expression (user defined literals are still normal functions, they can be called at runtime). Any idea how to implement this?

user7769147
  • 1,559
  • 9
  • 15
  • 2
    Perhaps use templates instead, like e.g. `std::basic_string` does to handle different character types? You could also do the Microsoft way and use a macro to automatically add a suitable prefix for all string literals, like e.g. `#define T(s) L ## s` or similar, and then use `foo(T("Hello world"))`. – Some programmer dude Jun 23 '20 at 10:38
  • `std::basic_string` cannot be converted to `std::basic_string` is T and U are not equal, furthermore it can't be used at compile time. I never heard about this Microsoft way, how does it work? – user7769147 Jun 23 '20 at 10:42
  • A pointer needs memory to point at. A consteval function cannot magically produce memory out of nowhere. – n. m. could be an AI Jun 23 '20 at 11:31
  • @n. 'pronouns' m. actually using templates for example it's possible to concatenate strings at compile time. [this answer](https://stackoverflow.com/a/59448568/7769147) to another post explains how to do it even if it only works with lvalues of std::string_view – user7769147 Jun 23 '20 at 13:09
  • Hmm, looks like non-type template parameters are needed for this, which are not yet implemented everywhere. – n. m. could be an AI Jun 23 '20 at 15:59

1 Answers1

3

This conversion is possible through a two-step process: first, by declaring a class that can convert a const char * to a char_type array within a compile-time constructor; second, by using that class within a user-defined literal:

#include <algorithm>

template<std::size_t N>
struct string_convert {
    char_type str[N] = {};

    consteval string_convert(const char (&s)[N]) {
        std::copy(s, s + N, str);
    }
};

template<string_convert SC>
consteval auto operator ""_s()
{
    return SC.str;
}

This interface allows for the following use:

void foo(const char_type *s);

foo("Hello, world!"_s);

Try it on Godbolt. Note that neither string_convert nor the user-defined literal appear in the disassembly; all that remains is the converted array.

clyne
  • 682
  • 4
  • 11
  • 1
    Awesome solution! You can also change the constructor to `consteval string_convert(const char(&s)[N])` so that you can omit the deduction guide – user7769147 Jun 23 '20 at 14:22