Basically what I'd like to do is build a string class that if handed a static string, or a constexpr variable pointing at one, it will simply use that pointer and won't later try to delete it.
I think we're facing an XY-problem here. Knowing whether an expression is or is not constexpr
won't tell you whether or not it is appropriate to delete
.
I would say the function shouldn't try to guess whether or not to delete if there is a possibility that this action is inappropriate. I believe the caller should take care of that, possibly using smart pointers to not take care of that.
In other words, I would let the function accept an lvalue reference, and let the client dereference a pointer if the pointed object must be passed as an argument to the function.
I would say this pattern makes sense not just in the case of delete
: more generally, if a piece of code has the responsibility of deciding how some values should be generated or computed (e.g. object allocation), then also the responsibility for correctly carrying out some related or corresponding action (e.g. cleanup) should belong to the same piece of code:
void foo(my_object& o)
{
// ...
}
my_object o;
foo(o);
// The argument has automatic storage, no need to delete
my_object* pO = new my_object();
foo(*pO);
// The argument was allocated through operator new, we must
// deallocate it through a corresponding call to delete
delete pO;
If you really want the cleanup to take place in the function, you should give the client a way to tell the function how to perform it:
void foo(my_object& o, bool shouldDelete)
{
// ...
if (shouldDelete) { delete &o; }
}
my_object o;
foo(o, false); // "And don't try to deallocate this object please..."
my_object* pO = new my_object();
foo(*pO, true); // "Please, delete it for me" (we are delegating the
// responsibility of performing the material action,
// but not the one of knowing how!)
To allow for greater flexibility, you could even accept a callable object, which makes even clearer what I meant in the above comment by "delegating the responsibility of performing the material action, but not the one of knowing how":
#include <functional>
void foo(my_object& o, std::function<void()> f = [] () { })
{
// ...
f();
}
int main()
{
my_object o;
foo(o); // "And don't do anything with this object when you're done..."
my_object* pO = new my_object();
foo(*pO, [=] () { delete pO; }); // "Please, do exactly this when done..."
}
If you do not need to determine the type of the callable object at run-time, you may even consider turning foo()
into a function template.
Finally, concerning your original question on how to determine whether an expression is a constant expression, this is not possible in general, but there are some techniques which could help you in cetain situations - just be aware of their limitations. In this regard, you may find this Q&A on StackOverflow relevant.