0

In C++20 it is possible to write a wrapper class1 which only accepts string literals.

struct string_literal
{
    template<size_t N>
    consteval string_literal(char const (&s)[N]) : p(s) {}

    char const* p;
};

void takes_literal(string_literal lit) {
  // use lit.p here
}

Is it also possible to write a concept that only matches string literals?


1 This was the original question, but per the comments and answers here: the premise is flawed: it seems that this construct does not, in fact, accept only string literals.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • 1
    A concept is a template that defines constraints on its template arguments. Where does a string literal fits in there? – n. m. could be an AI Dec 26 '22 at 18:51
  • I guess `Wrapper` should be spelled `string_literal` in the code example? – prapin Dec 26 '22 at 18:57
  • 5
    `char const (&s)[N]` is not string literal though, it can as well match an array. https://godbolt.org/z/Mn83bE5oW – apple apple Dec 26 '22 at 19:24
  • @appleapple - very good point. Could it match an array of less than static lifetime too? Or does the "must have linkage" requirement mean anything that matches will have static lifetime? – BeeOnRope Jan 04 '23 at 20:12
  • 1
    @BeeOnRope it can match, but you cannot get the pointer to it (as constant expression). – apple apple Jan 04 '23 at 20:46
  • Thanks. So it is fair to say that this construct (`consteval` function getting the pointer) would probably ensure static pointer lifetime, but not "constness" (nor as you point out, zero-termination)? – BeeOnRope Jan 04 '23 at 21:11
  • @n.m. - well a string literal has some type, and perhaps that is unique enough to allow it to be modeled by a concept that would reject most or all other types (but perhaps not). – BeeOnRope Jan 05 '23 at 12:29
  • @BeeOnRope probably. (except for what @‌NicolBolas point out) – apple apple Jan 05 '23 at 15:48

2 Answers2

2

A char const* which points into a string literal is virtually no different from any other char const*. The fact that a literal starts its life as an array is irrelevant, as non-literal character arrays can also be created. To my knowledge, there is exactly one difference: pointers to string literals cannot be used as non-type template parameters.

That's not particularly helpful. Even if we were restricted to compile-time code execution, you can get non-literal char const*s which also cannot be used as NTTPs (by creating a std::string, which can now be done at compile-time. You can call constexpr functions on string::c_strs pointer, but you can't use the result as an NTTP).

The best you can do is to create a user-defined literal operator that returns your string_literal object. You can make it so that only this literal operator can construct such a type (besides copying, of course). But even then, they can call your operator"" directly with a non-literal, and there is nothing you can do about it.

Instead, you should re-evaluate why you need to know if it is a string literal specifically.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • Hi Nicol, you are are right that "string literal only" wasn't the _true_ underlying requirement. Really, I want to ensure that the passed `const char *` (or something convertible to it) has indefinite (static) lifetime, i.e., that I freely use this pointer for the life of the program without worrying about lifetime issues. The overwhelmingly common case is that a string literal will be passed, which meets these requirements and I wanted to effectively "enforce" that. – BeeOnRope Dec 30 '22 at 14:58
  • It would also be OK if some other thing not a string literal, was passed in e.g., a static const char array (as you point out these are indistinguishable), but it would be problematic if (a) something with a shorter lifetime was passed or (b) something whose value may change in the future is passed. It seems that with my construct (b) is already possible (@apple apple gives an example in the comments) and now I'm worried it may not even guarantee a. – BeeOnRope Dec 30 '22 at 15:00
  • @BeeOnRope: It sounds to me like you're trying to avoid copying a string. Are you certain that this really worthwhile in whatever you're making? I mean, static strings tend to be on the short side after all, so SSO could cover those cases. At the very least, I hope that this is driven by some kind of profiling. – Nicol Bolas Dec 30 '22 at 15:03
  • Yes, I am trying to avoid copying a string. I am absolutely certain it is worth it here, the difference is close to 10x if SSO gets used or 100x otherwise with production-accurate in-situ profiling as well as assembly inspection. I can appreciate your concern but I hope you can trust me that here I want a pointer of static lifetime. – BeeOnRope Dec 30 '22 at 15:06
  • 1
    @BeeOnRope: The thing is, there is no distinction between a static string and a non-static string. At all. At least literals have that one thing that they can't be used with (NTTPs), but static strings aren't even that. Thanks to compile-time memory allocation, you can pass non-static string pointers to `consteval` functions just fine as long as this happens during constant evaluation. There simply isn't a way to differentiate between them. The absolute most you can do is provide a way for the user to tell you if the string is static. – Nicol Bolas Dec 30 '22 at 15:15
  • Where's there's some difference between static and non-static in that the former can be passed to a `consteval` function outside of constant evaluation, right? That might be enough if the callee is under my control and is not `consteval`, e.g., the `string_literal` class above used for parameters to a non-`consteval/expr` function. – BeeOnRope Jan 01 '23 at 18:24
1

Whereas const char(&)[N] isn't necessary a string literal, you might create concept to match const char(&)[N]:

template <typename T>
struct is_str_literal_impl : std::false_type{};

template <std::size_t N>
struct is_str_literal_impl<const char[N]> : std::true_type{};

template <typename T>
concept concept_str_literal = is_str_literal_impl<T>::value;

void takes_literal(concept_str_literal auto&  lit) {
  // use lit.p here
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302