Let's say I have these two overloads:
void Log(const wchar_t* message)
{
// Do something
}
void Log(const std::wstring& message)
{
// Do something
}
Can I then in the first function add some compile-time verifiction that the passed argument is a string literal?
EDIT: A clarification on why this would be good in my case; my current high-frequency logging uses only string literals and can hence be optimized a lot when there are non-heap allocation guarantees. The second overload doesn't exist today, but I might want to add it, but then I want to keep the first one for extreme scenarios. :)
The compiler scans the source code file, looks for, and stores all occurrences of string literals. It can use a mechanism such as a lookup table to do this. It then runs through the list and assigns the same address to all identical string literals.
A "string literal" is a sequence of characters from the source character set enclosed in double quotation marks (" "). String literals are used to represent a sequence of characters which, taken together, form a null-terminated string.
The only difference is that you cannot modify string literals, whereas you can modify arrays. Functions that take a C-style string will be just as happy to accept string literals unless they modify the string (in which case your program will crash).
freshair is not a valid string literal. Hence, option(c) is correct option. String are data types is used to store sequence of characters, variables, numbers, or symbols. The string can be declared in double quotes or single quotes.
You can't detect string literals directly but you can detect if the argument is an array of characters which is pretty close. However, you can't do it from the inside, you need to do it from the outside:
template <std::size_t Size>
void Log(wchar_t const (&message)[Size]) {
// the message is probably a string literal
Log(static_cast<wchar_t const*>(message);
}
The above function will take care of wide string literals and arrays of wide characters:
Log(L"literal as demanded");
wchar_t non_literal[] = { "this is not a literal" };
Log(non_literal); // will still call the array version
Note that the information about the string being a literal isn't as useful as one might hope for. I frequently think that the information could be used to avoid computing the string length but, unfortunately, string literals can still embed null characters which messes up static deduction of the string length.
So this grew out of Keith Thompson's answer... As far as I know, you can't restrict string literals to only normal functions, but you can do it to macro functions (through a trick).
#include <iostream>
#define LOG(arg) Log(L"" arg)
void Log(const wchar_t *message) {
std::wcout << "Log: " << message << "\n";
}
int main() {
const wchar_t *s = L"Not this message";
LOG(L"hello world"); // works
LOG(s); // terrible looking compiler error
}
Basically, a compiler will convert "abc" "def"
to look exactly like "abcdef"
. And likewise, it will convert "" "abc"
to "abc"
. You can use this to your benefit in this case.
I also saw this comment on the C++ Lounge, and that gave me another idea of how to do this, which gives a cleaner error message:
#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)
Here, we use the fact that static_assert requires a string literal as it's second argument. The error that we get if we pass a variable instead is quite nice as well:
foo.cc:12:9: error: expected string literal
LOG(s);
^
foo.cc:3:43: note: expanded from macro 'LOG'
#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)
I believe the answer to your question is no -- but here's a way to do something similar.
Define a macro, and use the #
"stringification" operator to guarantee that only a string literal will be passed to the function (unless somebody bypasses the macro and calls the function directly). For example:
#include <iostream>
#define LOG(arg) Log(#arg)
void Log(const char *message) {
std::cout << "Log: " << message << "\n";
}
int main() {
const char *s = "Not this message";
LOG("hello world");
LOG(hello world);
LOG(s);
}
The output is:
Log: "hello world"
Log: hello world
Log: s
The attempt to pass s
to LOG()
did not trigger a compile-time diagnostic, but it didn't pass that pointer to the Log
function.
There are at least two disadvantages to this approach.
One is that it's easily bypassed; you may be able to avoid that by searching the source code for references to the actual function name.
The other is that stringifying a string literal doesn't just give you the same string literal; the stringified version of "hello, world"
is "\"hello, world\""
. I suppose your Log
function could strip out any "
characters in the passed string. You may also want to handle backslash escapes; for example, "\n"
(a 1-character string containing a newline) is stringified as "\\n"
(a 2-character string containing a backslash and the letter n
).
But I think a better approach is not to rely on the compiler to diagnose calls with arguments other than string literals. Just use some other tool to scan the source code for calls to your Log
function and report any calls where the first argument isn't a string literal. If you can enforce a particular layout for the calls (for example, the tokens Log
, (
, and a string literal on the same line), that shouldn't be too difficult.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With