Reputation: 17724
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?
Upvotes: 11
Views: 1999
Reputation: 136435
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.
Upvotes: 1
Reputation: 64996
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.
Upvotes: 6