10

I'm wondering if it's possible in C++ to declare a function parameter that must be a string literal? My goal is to receive an object that I can keep only the pointer to and know it won't be free()ed out from under me (i.e. has application lifetime scope).

For example, say I have something like:

#include <string.h>

struct Example {
    Example(const char *s) : string(s) { }

    const char *string;
};

void f() {
    char *freeableFoo = strdup("foo");

    Example e(freeableFoo);     // e.string's lifetime is unknown
    Example e1("literalFoo");   // e1.string is always valid

    free(freeableFoo);

    // e.string is now invalid
}

As shown in the example, when freeableFoo is free()ed the e.string member becomes invalid. This happens without Example's awareness.

Obviously we can get around this if Example copies the string in its constructor, but I'd like to not allocate memory for a copy.

Is a declaration possible for Example's constructor that says "you must pass a string literal" (enforced at compile-time) so Example knows it doesn't have to copy the string and it knows its string pointer will be valid for the application's lifetime?

par
  • 17,361
  • 4
  • 65
  • 80
  • 1
    I think this is a dup of https://stackoverflow.com/questions/57543521/how-to-ensure-arguments-point-to-objects-in-static-storage-duration-only but that question doesn't have any answers either – CherryDT Sep 09 '21 at 20:29
  • 1
    What is its usage? – Ghasem Ramezani Sep 09 '21 at 20:30
  • @CherryDT I can see how what you're saying can happen, but it doesn't really change my question... I want to know if there's a way to enforce at compile time that a pointer's content can't change. – par Sep 09 '21 at 20:30
  • You can possibly achieve whatever it is you want to achieve using `std::unique_ptr` or `std::shared_ptr`. What is your ultimate goal here? – Galik Sep 09 '21 at 20:31
  • @par: I realized it can't happen, because the case I was thinking of was actually using macros to _appear_ to work this way, while in reality some additional things happened, it wasn't transparent. I now found out that string literals are guaranteed to have the lifetime of the program because they have static storage duration, so I deleted the original comment. – CherryDT Sep 09 '21 at 20:31
  • @CherryDT yes it looks like my question is a duplicate. I didn't find that one while searching the site, but basically it's the same question. Bummer :( – par Sep 09 '21 at 20:31
  • I"m pretty sure there isn't something like this on the constructor level. You may be able to declare the variable constexpr though, but applying constexpr to the constructor simply implies that the result can be a constexpr, not that it must be one (and you'd be able to pass `nullptr` too). – fabian Sep 09 '21 at 20:34
  • The goal is to just store a pointer and know it's immutable. In `Swift` for example a string can be declared `let foo = "foo"` and everything it is passed to will (behind the scenes) get the same pointer that will always be valid as long as there is a reference to it somewhere (and its content doesn't have to be copied). – par Sep 09 '21 at 20:34
  • @par that kind of logic is accomplished in C++ using `std::shared_ptr`. You are not going to be able to solve this with raw pointers, you need some kind of data management system, so use the one that the C++ standard provides for this task. To avoid copying the the string data when creating the initial `shared_ptr`, you could provide it with a raw pointer to the string literal and a do-nothing deleter. – Remy Lebeau Sep 09 '21 at 20:37
  • @RemyLebeau My embedded project doesn't have room for the C++ standard library. That's why a (say) `constexpr` argument would be useful. – par Sep 09 '21 at 20:40
  • You might be interested in [passing string literals as template parameters](https://stackoverflow.com/a/68790828/2752075). – HolyBlackCat Sep 09 '21 at 20:43
  • 4
    @par You can do it in C++20. Like [this](https://godbolt.org/z/fMoaP5cTa) Can simplify [a bit](https://godbolt.org/z/e3qWrMdqd) or make it easier on eyes. – C.M. Sep 09 '21 at 21:06
  • How many strings would you be copying here? Because if these are meant to be string *literals*, it would have to be an immense number of literals (ostensibly all manually typed) or objects holding copies of them for the size to make any appreciable difference. Are you sure you this isn't an attempt at premature optimization? – Joe Sep 09 '21 at 21:15
  • @Joe I working with about 18KB free heap currently, so I'm doing actual optimization ;) ... Besides, it seems like a useful language feature worth discussing. – par Sep 09 '21 at 21:42
  • @C.M. I don't have C++20 available for my project without building a new toolchain, but this seems like a great potential answer. – par Sep 09 '21 at 21:48
  • 2
    @par I don't see how you could make it without `consteval` (which is present only in C++20). Unless you find another mechanism that forces compiler to complain if expression/argument is not "known" at compile time. – C.M. Sep 09 '21 at 21:55
  • 1
    While not 100% bullet proof, we have been using `LiteralString` is our code base with no problems for years now. Here is a very simplified version: `struct LiteralString { template constexpr LiteralString(const char (&str)[N]) : string(str) {} const char* string; };` – prapin Sep 10 '21 at 08:42
  • @C.M. - one problem is that your approach will also match mutable static char arrays, like `char mut_string[] = {'a'}` at namespace scope. I think it's better if you get rid of the `remove_cv` part and check explicitly if it's the same as `const char`, since this may mostly (completely?) eliminate that possibility. I wrote an answer based on your idea and this observation. – BeeOnRope Jan 05 '23 at 12:43
  • Possibly, passing the string literal as a template parameter would be appropriate here, depending on what the ultimate goal is. – CoffeeTableEspresso Jan 05 '23 at 20:23
  • @CoffeeTableEspresso pre-C++17ish, string literals couldn't be templat eparameters. You could make a `const char&[N]` template parameter, but that doesn't provide the desired guarantees. – Mooing Duck Jan 05 '23 at 20:50
  • @BeeOnRope Did you look at simplified version? (2nd link in my initial comment) Original request was to detect if string is "safe" (i.e. it's pointer never gets bad) -- that's why (probably) I've included non-const char arrays too. – C.M. Jan 06 '23 at 15:58

3 Answers3

5

In C++20 you can do it using a wrapper class that has a consteval converting constructor which takes a string literal:

struct literal_wrapper
{
    template<class T, std::size_t N, std::enable_if_t<std::is_same_v<T, const char>>...>
    consteval literal_wrapper(T (&s)[N]) : p(s) {}

    char const* p;
};

The idea is that string literals have type const char[N] and we match this.

Then you can use this wrapper class in places where want to enforce passing a string literal:

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

You can call this as foo("foobar").

Note that this will also match static-storage const char[] arrays, like so:

const char array[] = {'a'};


takes_literal(array); // this compiles

Static arrays have most of the same characteristics as string literals, however, e.g., indefinite storage duration, which may work for you.

It does not match local arrays because the decayed pointer value is not a constant expression (that's where consteval comes in).

This answer is almost directly copied from the first variant suggested in C.M.'s comment on the question.

BeeOnRope
  • 60,350
  • 16
  • 207
  • 386
  • for readers: also see relevant discussions here https://stackoverflow.com/questions/74922986/c20-concept-matching-string-literal – apple apple Jan 05 '23 at 15:43
1

if it's possible in C++ to declare a function parameter that must be a string literal?

In C++ string literals have type char const[N], so that you can declare a parameter to be of such a type:

struct Example {
    template<size_t N>
    Example(char const(&string_literal)[N]); // In C++20 declare this constructor consteval.

    template<size_t N>
    Example(char(&)[N]) = delete; // Reject non-const char[N] arguments.
};

However, not every char const[N] is a string literal. One can have local variables and data members of such types. In C++20 you can declare the constructor as consteval to make it reject non-literal arguments for string_literal parameter.


Conceptually, you'd like to determine the storage duration of the argument to a constructor/function parameter. Or, more precisely, whether the argument has a longer lifetime than Example::string reference to it. C++ doesn't provide that, C++20 consteval is still a poor-man's proxy for that.

gcc extension __builtin_constant_p detetmines whether an expression is a compile-time constant which is widely used in preprocessor macros in Linux kernel source code. However, it can only evaluate to 1 on expressions, but never on functions' parameters, so that its use is limited to preprocessor macros.


The traditional solution for the problem of different object lifetimes has been organizing objects into a hierarchy, where objects at lower levels have smaller lifetimes than objects at higher levels, and hence, an object can always have a plain pointer to an object at a higher level of hierarchy valid. This approach is somewhat advanced, labour intensive and error prone, but it totally obviates the need for any garbage collection or smart-pointers, so that it's only used in ultra critical applications where no cost is too high. The opposite extreme of this approach is using std::shared_ptr/std::weak_ptr for everything which snowballs into maintenance nightmare pretty rapidly.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

Just make the constructor explicitly taking rvalue:

struct Example {
   Example(const char*&& s) : string(s) { }

   const char* string;
};
Damir Tenishev
  • 1,275
  • 3
  • 14