Подтвердить что ты не робот

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

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

Например,

#define is_string_literal(X)
...
...   

is_string_literal("hello") == true;
const char * p = "hello";
is_string_literal(p) == false;

или

bool is_string_literal(const char * s);

is_string_literal("hello") == true;
const char * p = "hello";
is_string_literal(p) == false;

Спасибо.

4b9b3361

Ответ 1

ДА! (Спасибо Джеймс Макнеллис и GMan для исправлений. Обновлено для правильной обработки объединенных литералов типа "Hello, " "World!" которые вырезаются до конкатенации.)

#define is_literal_(x) is_literal_f(#x, sizeof(#x) - 1)
#define is_literal(x) is_literal_(x)

bool is_literal_f(const char *s, size_t l)
{
    const char *e = s + l;
    if(s[0] == 'L') s++;
    if(s[0] != '"') return false;
    for(; s != e; s = strchr(s + 1, '"'))
      {
        if(s == NULL) return false;
        s++;
        while(isspace(*s)) s++;
        if(*s != '"') return false;
      }
    return true;
}

Это приведет к форматированию аргумента перед передачей его функции, поэтому, если аргумент был строковым литералом, аргумент, переданный нашей функции, будет окружен кавычками.

Если вы считаете это строковым литералом:

const char *p = "string";
// should is_literal(p) be true or false?

Я не могу вам помочь. Возможно, вы сможете использовать определенное (или * shudder * undefined) поведение, определяемое реализацией, для проверки того, хранится ли строка в постоянной памяти, но некоторые (возможно, более старые) системы p могут быть изменены.

Для тех, кто ставит под вопрос использование такой функции, рассмотрим:

enum string_type { LITERAL, ARRAY, POINTER };

void string_func(/*const? */char *c, enum string_type t);

Вместо того, чтобы явно указывать второй аргумент string_function для каждого вызова, is_literal позволяет нам обернуть его макросом:

#define string_func(s) \
    (string_func)(s, is_literal(s)  ? LITERAL :
        (void *)s == (void *)&s ? ARRAY : POINTER)

Я не могу себе представить, почему это изменило бы ситуацию, за исключением простого C, где литералы не являются const и по какой-то причине вы не хотите/не можете записать функцию как взяв const char * вместо char. Но есть все основания, чтобы хотеть что-то сделать. Когда-нибудь вы тоже почувствуете необходимость прибегать к ужасному взлому.

Ответ 2

Знание во время компиляции (как упоминалось в вопросе), со следующей методикой. Вы можете определить, является ли данный аргумент строковым литералом или нет. Если это какой-то массив или указатель вроде const char x[], *p; то это вызовет ошибку компилятора.

#define is_string_literal(X) _is_string_literal("" X)
bool _is_string_literal (const char *str) { return true; } // practically not needed

[Примечание: мой предыдущий ответ был отклонен экспертами, и он еще не принят или проголосовал после внесенных изменений. Я помещаю другой ответ с тем же контентом.]

Ответ 3

Нет. Строковый литерал - это всего лишь массив char (в C) или const char (в С++).

Вы не можете различать строковый литерал и другой массив char, как этот (в С++):

const char x[] = "Hello, World!";

Ответ 4

Попробуйте следующее:

#define is_string_literal(s) \
  (memcmp(#s, "\"", 1) == 0)

В соответствии с соглашением об именовании переменных C/С++ имя переменной должно начинаться с "_" или алфавита.

Ответ 5

У меня был аналогичный вопрос: я хотел сказать, что

MY_MACRO("compile-time string")

был законным и что

char buffer[200]="a string";
MY_MACRO(buffer)

является законным, но не позволяет

MY_MACRO(szArbitraryDynamicString)

Я использовал GCC __builtin_types_compatible_p и MSVC _countof, которые, похоже, работают правильно за счет отказа от коротких строковых литералов.

Ответ 6

Поскольку строковый литерал в c++ может иметь разные префиксы, нет смысла проверять начальную цитату: https://en.cppreference.com/w/cpp/language/string_literal

Лучше проверить окончательную цитату:

  • c++ 11
  • msvc2015u3, gcc5.4, clang3.8.0

    #include <type_traits>
    
    #define UTILITY_CONST_EXPR_VALUE(exp)                   ::utility::const_expr_value<decltype(exp), exp>::value
    
    // hint: operator* applies to character literals, but not to double-quoted literals
    #define UTILITY_LITERAL_CHAR_(c_str, char_type)         UTILITY_CONST_EXPR_VALUE(((void)(c_str * 0), ::utility::literal_char_caster<typename ::utility::remove_cvref<char_type>::type>::cast_from(c_str, L ## c_str, u ## c_str, U ## c_str)))
    #define UTILITY_LITERAL_CHAR(c_str, char_type)          UTILITY_LITERAL_CHAR_(c_str, char_type)
    
    #define UTILITY_IS_LITERAL_STRING(c_str)                UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == UTILITY_LITERAL_CHAR('\"', decltype(c_str[0])) : false)
    
    #define UTILITY_IS_LITERAL_STRING_A(c_str)                   UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == '\"' : false)
    #define UTILITY_IS_LITERAL_STRING_WITH_PREFIX(c_str, prefix) UTILITY_CONST_EXPR_VALUE((sizeof(#c_str) > 1) ? #c_str [sizeof(#c_str) - 2] == prefix ## '\"' : false)
    
    namespace utility {
    
        template <typename T, T v>
        struct const_expr_value
        {
            static constexpr const T value = v;
        };
    
        // remove_reference + remove_cv
        template <typename T>
        struct remove_cvref
        {
            using type = typename std::remove_cv<typename std::remove_reference<T>::type>::type;
        };
    
        //// literal_char_caster, literal_string_caster
    
        // template class to replace partial function specialization and avoid overload over different return types
        template <typename CharT>
        struct literal_char_caster;
    
        template <>
        struct literal_char_caster<char>
        {
            static inline constexpr char
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return ach;
            }
        };
    
        template <>
        struct literal_char_caster<wchar_t>
        {
            static inline constexpr wchar_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return wch;
            }
        };
    
        template <>
        struct literal_char_caster<char16_t>
        {
            static inline constexpr char16_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return char16ch;
            }
        };
    
        template <>
        struct literal_char_caster<char32_t>
        {
            static inline constexpr char32_t
                cast_from(
                    char ach,
                    wchar_t wch,
                    char16_t char16ch,
                    char32_t char32ch)
            {
                return char32ch;
            }
        };
    
    }
    
    const char * a = "123";
    const char b[] = "345";
    
    int main()
    {
        static_assert(UTILITY_IS_LITERAL_STRING_A(a) == 0, "Aa");
        static_assert(UTILITY_IS_LITERAL_STRING(a) == 0, "a");
        static_assert(UTILITY_IS_LITERAL_STRING_A(b) == 0, "Ab");
        static_assert(UTILITY_IS_LITERAL_STRING(b) == 0, "b");
        static_assert(UTILITY_IS_LITERAL_STRING_A("123") == 1, "A123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(L"123", L) == 1, "L123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(u"123", u) == 1, "u123");
        static_assert(UTILITY_IS_LITERAL_STRING_WITH_PREFIX(U"123", U) == 1, "U123");
        static_assert(UTILITY_IS_LITERAL_STRING("123") == 1, "123");
        static_assert(UTILITY_IS_LITERAL_STRING(L"123") == 1, "L123");
        static_assert(UTILITY_IS_LITERAL_STRING(u"123") == 1, "u123");
        static_assert(UTILITY_IS_LITERAL_STRING(U"123") == 1, "U123");
    }
    

https://godbolt.org/z/UXIRY6

Макрос UTILITY_CONST_EXPR_VALUE требуется, чтобы заставить компилятор генерировать код только во время компиляции.