Может ли ошибка компиляции быть принудительно, если строковый аргумент не является строковым литералом?
Скажем, у меня эти две перегрузки:
void Log(const wchar_t* message)
{
// Do something
}
void Log(const std::wstring& message)
{
// Do something
}
Могу ли я тогда в первой функции добавить некоторую проверку времени компиляции, чтобы переданный аргумент был строковым литералом?
РЕДАКТИРОВАТЬ: Прояснение того, почему это было бы хорошо в моем случае; мое текущее высокочастотное протоколирование использует строковые литералы только и, следовательно, может быть оптимизировано много, если существуют гарантии распределения без кучи. Вторая перегрузка сегодня не существует, но я могу добавить ее, но тогда я хочу сохранить первую для экстремальных сценариев.:)
Ответы
Ответ 1
Итак, это выросло из ответа Keith Thompson... Насколько я знаю, вы не можете ограничивать строковые литералы только нормальными функциями, но вы можете сделать это к макрофункциям (через трюк).
#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
}
В принципе, компилятор преобразует "abc" "def"
, чтобы выглядеть точно как "abcdef"
. Точно так же он преобразует "" "abc"
в "abc"
. Вы можете использовать это в свою пользу в этом случае.
Я также видел этот комментарий в C + + Lounge, и это дало мне еще одну идею о том, как это сделать, что дает очиститель сообщение об ошибке:
#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)
Здесь мы используем тот факт, что static_assert требует строкового литерала в качестве второго аргумента. Ошибка, которую мы получаем, если мы передаем переменную, тоже неплоха:
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)
Ответ 2
Я считаю, что ответ на ваш вопрос - нет, но вот способ сделать что-то подобное.
Определите макрос и используйте оператор #
"stringification", чтобы гарантировать, что к функции будет передан только строковый литерал (если кто-то не обходит макрос и не вызывает функцию напрямую). Например:
#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);
}
Вывод:
Log: "hello world"
Log: hello world
Log: s
Попытка передать s
в LOG()
не запускала диагностику времени компиляции, но она не передавала этот указатель на функцию Log
.
При таком подходе есть как минимум два недостатка.
Во-первых, это легко обойти; вы можете избежать этого, выполнив поиск исходного кода для ссылок на фактическое имя функции.
Другим является то, что строгая строковый литерал не просто дает вам тот же строковый литерал; строковая версия "hello, world"
равна "\"hello, world\""
. Я полагаю, что ваша функция Log
может вычеркнуть любые символы "
в переданной строке. Вы также можете обращаться с обратными слэшами; например, "\n"
(1-символьная строка, содержащая строку новой строки) стробируется как "\\n"
(2-символьная строка, содержащая обратную косую черту и букву n
).
Но я думаю, что лучший подход заключается не в том, чтобы полагаться на компилятор для диагностики вызовов с помощью аргументов, отличных от строковых литералов. Просто используйте другой инструмент для сканирования исходного кода для вызовов вашей функции Log
и сообщите о любых вызовах, где первый аргумент не является строковым литералом. Если вы можете применить конкретный макет для вызовов (например, токены Log
, (
и строковый литерал в той же строке), это не должно быть слишком сложно.
Ответ 3
Вы не можете напрямую определить строковые литералы, но вы можете определить, является ли аргумент массивом символов, который довольно близок. Однако вы не можете сделать это изнутри, вам нужно сделать это снаружи:
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);
}
Вышеупомянутая функция позаботится о широких строковых литералах и массивах широких символов:
Log(L"literal as demanded");
wchar_t non_literal[] = { "this is not a literal" };
Log(non_literal); // will still call the array version
Обратите внимание, что информация о том, что строка является литералом, не так полезна, как можно было бы надеяться. Я часто думаю, что эту информацию можно использовать, чтобы избежать вычисления длины строки, но, к сожалению, строковые литералы все еще могут вставлять пустые символы, которые мешают статическому вычитанию длины строки.
Ответ 4
Если вы определяете Log
в качестве макроса, а вызовите отдельные методы для обработки литерала или std::wstring
, следует использовать некоторые изменения следующего:
#define Log(x) ((0[#x] == 'L' && 1[#x] == '"') ? LogLiteral(x) : LogString(x))
void
LogLiteral (const wchar_t *s) {
//...do something
}
void
LogString (const std::wstring& s) {
//...do something
}
Хитрость в том, что вам нужны противоположные определения LogLiteral()
, чтобы компиляция прошла, но ее никогда не следует вызывать.
inline void LogLiteral (const std::wstring &s) {
throw std::invalid_argument(__func__);
}
Этот код дает вам поведение перегруженного метода Log()
, поскольку вы можете передать строковый литерал или нестроковый литерал в макрос Log()
, и в итоге он вызовет либо LogLiteral()
, либо LogString()
. Это дает возможность проверять время компиляции, поскольку компилятор ничего не пропускает, кроме того, что код распознает как строковый литерал для вызова LogLiteral()
. При достаточной оптимизации условная ветвь может быть удалена, поскольку каждый экземпляр проверки статичен (на GCC удаляется).
Ответ 5
Я не думаю, что вы можете принудительно передать только строковый литерал для функции, но литералы - это массивы символов, что вы можете обеспечить:
#include <iostream>
template<typename T>
void log(T) = delete; //Disable everything
template <std::size_t Size>
void log(const wchar_t (&message)[Size]) //... but const wchar_t arrays
{
std::cout << "yay" << std::endl;
}
const wchar_t * get_str() { return L"meow"; }
int main() {
log(L"foo"); //OK
wchar_t arr[] = { 'b', 'a', 'r', '0' };
log(arr); //Meh..
// log(get_str()); //compile error
}
Даунсайд - это то, что если у вас есть массив символов времени исполнения, он также будет работать, но не будет работать для обычных строк стиля c-времени исполнения.
Но если вы можете работать с немного другим синтаксисом, тогда ответ будет ДА:
#include <cstddef>
#include <iostream>
void operator"" _log ( const wchar_t* str, size_t size ) {
std::cout << "yay" << std::endl;
}
int main() {
L"Message"_log;
}
Конечно, для обоих решений нужен С++ 11-совместимый компилятор (пример проверен с помощью g++ 4.7.3).
Ответ 6
Вот быстрый пример, который я просто взломал с помощью printf
hack, предложенного в комментариях выше:
#include <cstdio>
#define LOG_MACRO(x) do { if (0) printf(x); Log(x); } while (0)
void Log(const char *message)
{
// do something
}
void function(void)
{
const char *s = "foo";
LOG_MACRO(s);
LOG_MACRO("bar");
}
Результат компиляции этого с помощью Clang выглядит именно так, как вы хотите:
$ clang++ -c -o example.o example.cpp
example.cpp:13:15: warning: format string is not a string literal
(potentially insecure) [-Wformat-security]
LOG_MACRO(s);
^
example.cpp:3:41: note: expanded from macro 'LOG_MACRO'
#define LOG_MACRO(x) do { if (0) printf(x); Log(x); } while (0)
^
1 warning generated.
Мне пришлось переключиться на printf
, а не на wprintf
, так как последнее, похоже, не генерирует предупреждение - я предполагаю, что, вероятно, ошибка Clang.
Вывод GCC аналогичен:
$ g++ -c -o example.o example.cpp
example.cpp: In function ‘void function()’:
example.cpp:13: warning: format not a string literal and no format arguments
example.cpp:13: warning: format not a string literal and no format arguments
Изменить: вы можете увидеть ошибку Clang здесь. Я только что добавил комментарий о -Wformat-security
.
Ответ 7
Добавление этой альтернативы для дальнейшего использования. Он исходит из вопроса SO Возможно ли перегрузить функцию, которая может указать фиксированный массив из указателя?
#include <iostream>
#include <type_traits>
template<typename T>
std::enable_if_t<std::is_pointer<T>::value>
foo(T)
{
std::cout << "pointer\n";
}
template<typename T, unsigned sz>
void foo(T(&)[sz])
{
std::cout << "array\n";
}
int main()
{
char const* c = nullptr;
char d[] = "qwerty";
foo(c);
foo(d);
foo("hello");
}
Вышеприведенный фрагмент компилируется и отлично работает на http://webcompiler.cloudapp.net/