Может ли ошибка компиляции быть принудительно, если строковый аргумент не является строковым литералом?

Скажем, у меня эти две перегрузки:

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/