Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

How to Check If a String is Literal in Compile-time (C++)

5.00/5 (10 votes)
30 Sep 2017CPOL2 min read 18.3K   134  
How to check if a string is literal in compile-time (C++)

Introduction

There are many cases when we need to determine if a string is literal. For example, one of such cases is logging optimization without formatting parameters in the worker thread. The most of logging parameters are string literals of the "text to log out" form. So, there is no need to use resources for copying such literals, because they can be transmitted as a pointer. However, it is necessary to define, how exactly the string is specified, and is it a string at all. What is more, we should define it in compile-time.

The method described below will do this. Code can be used both for char and wchar_t.

Strategy

In order to determine the type of parameter, we need to:

  • define if the parameter (without cv-qualifiers) is of types char or wchar_t
  • define if the parameter is an array
  • compare the value of the parameter with string #x (or L#x), preprocessed from this
    parameter, character-by-character
C++
static constexpr bool value(const __Char(&str1)[N], const __T& str2)
{
    return
        sizeof (str1) > sizeof(str2) &&
        _decay_equiv<__T, const __Char*>::value &&
        _is_array(str2) &&
        _is_same<decltype(str1), __T, 
         (sizeof(str2) / sizeof(__Char)) - 2>::value(str1, str2, _move_sz<__Char>::val);
}
static constexpr bool value(const __Char(&str1)[N], const char*& str2)
{
    return false;
}
static constexpr bool value(const __Char(&str1)[N], const wchar_t*& str2)
{
    return false;
}

The above str1 is a string, preprocessed from the parameter under consideration, str2 -
the parameter itself.

By Steps

  1. Use the structure below to compare the parameter type without cv-qualifiers:
    C++
    template <typename T, typename U> struct _decay_equiv : 
    std::is_same<typename std::decay<T>::type, U>::type {
    };
  2. Define if the parameter is an array of characters:
    C++
    template<uint32_t __Sz> static constexpr bool _is_array(const __Char(&d)[__Sz]) {
        return true;
    }
    static constexpr bool _is_array(...) {
        return false;
    }
  3. Compare with preprocessed string #x or L#x.

    If argument is literal string, it will look like "\"text\"" or "L\"text\"" and will contain characters 'L' and '\"'. The length of the preprocessed string will always be greater than the length of the string parameter (because rest of the cases are filtered by the conditions above). So, comparing string parameter with preprocessed string character-by-character, it is necessary to shift index of character, that is compared, by '\"' (for char) or by '\"' and 'L' (for wchar_t).

    C++
    template<typename __Ch> struct _move_sz {
        enum { val = 0 };
    };
    template<> struct _move_sz<char> {
        enum { val = 1 }; // "
    };
    template<> struct _move_sz<wchar_t> {
        enum { val = 2 }; // L"
    };

    Compare as recursion:

    C++
    template<typename __Type1, typename __Type2, int32_t __Ix> struct _is_same {
        static constexpr bool value(const __Type1& str1, const __Type2& str2, int32_t move) {
            return (str1[__Ix + move] == str2[__Ix]) && 
                   (_is_same<__Type1, __Type2, __Ix - 1>::value(str1, str2, move));
        }
    };
    template<typename __Type1, typename __Type2> struct _is_same<__Type1, __Type2, 0> {
        static constexpr bool value(const __Type1& str1, const __Type2& str2, int32_t move) {
            return (str1[move] == str2[0]);
        }
    };

    The above str1 is the preprocessed string, str2 - the parameter under consideration,
    move - used offset.

In the End

It is convenient to define the call as:

C++
#define is_literal_string_a(x) is_literal_string<sizeof(#x) / sizeof(char), 
decltype(x), char>::value(#x, x)
#define is_literal_string_w(x) is_literal_string<sizeof(L#x) / sizeof(wchar_t), 
decltype(x), wchar_t>::value(L#x, x)

Conclusion

The code below can be used for testing:

C++
int main()
{
    const char* h1 = "test_1";
    const wchar_t* h3 = L"test_3";
    const wchar_t h4[] = { L'g', L'g', L'g', L'g' };

    enum {
        v1 = is_literal_string_a(h1),
        v2 = is_literal_string_a("test_4"),
        v3 = is_literal_string_w(h3),
        v4 = is_literal_string_w(L"test_5"),
        v5 = is_literal_string_w(10),
        v6 = is_literal_string_w(h4),
    };

    printf("v1 = %d\n", v1);
    printf("v2 = %d\n", v2);
    printf("v3 = %d\n", v3);
    printf("v4 = %d\n", v4);
    printf("v5 = %d\n", v5);
    printf("v6 = %d\n", v6);
    return 0;
}

The code was tested in Visual Studio 2015.
Have a nice code!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)