c++overloadingsfinaetype-traitsenable-if

Overload resolution for char*, char array, and string literals using constexpr, SFINAE and/or type_traits


I have run into an interesting challenge that I have been trying to solve for hours, but after much research and many failed attempts, I find myself asking this question.

I would like to write 3 overloaded functions that each take one of the following types: const char*, const char(&)[N] and string literal (e.g. "BOO"). I understand that a string literal is simply a char array, but please bear with me while I explain my approach.

The two functions below are able to differentiate between the first two types (const char* and const char(&)[N]) thanks to the wrapper class CharPtrWrapper:

#include <iostream>

class CharPtrWrapper
{
public:
    CharPtrWrapper(const char* charPtr)
        : m_charPtr(charPtr)
    {

    }

    const char * m_charPtr;
};

void processStr(CharPtrWrapper charPtrWrapper)
{
    std::cout << "From function that takes a CharPtrWrapper = " << charPtrWrapper.m_charPtr << '\n';
}

template<std::size_t N>
void processStr(const char (&charArr)[N])
{
    std::cout << "From function that takes a \"const char(&)[N]\" = " << charArr << '\n';
}

int main()
{
    const char* charPtr = "ABC";
    processStr(charPtr);

    const char charArr[] = {'X', 'Y', 'Z', '\0'};
    processStr(charArr);
}

Output:

From function that takes a CharPtrWrapper = ABC
From function that takes a "const char(&)[N]" = XYZ

Now, if I call processStr with a string literal (e.g. processStr("BOO")), the version that takes a const char(&)[N] gets called, which makes sense, since a string literal is simply a char array.

Here is where I reach the crux of the problem. I have not been able to write a function that is able to differentiate between a char array and a string literal. One thing I thought might work was to write a version that takes an rvalue reference:

template<std::size_t N>
void processStr(const char (&&charArr)[N])
{
    std::cout << "From function that takes a \"const char(&&)[N]\" = " << charArr << '\n';
}

But it turns out that string literals are lvalues. I have also played with different versions that use std::enable_if and std::is_array, but I still don't get the result I'm looking for.

So I guess my question is the following: is it possible to differentiate between char arrays and string literals in modern C++?


Solution

  • Per [expr.prim.id.unqual]:

    [...] The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The expression is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise; it is a bit-field if the identifier designates a bit-field ([dcl.struct.bind]).

    Therefore, given a declaration

    const char arr[] = "foo";
    

    The expression arr is an lvalue of type const char[4].


    Per [lex.string]/8:

    Ordinary string literals and UTF-8 string literals are also referred to as narrow string literals. A narrow string literal has type “array of n const char”, where n is the size of the string as defined below, and has static storage duration.

    And per [expr.prim.literal]:

    A litera is a primary expression. Its type depends on its form. A string literal is an lvalue; all other literals are prvalues.

    Therefore, the expression "foo" is an lvalue of type const char[4].


    Conclusion: a function is unable to differentiate between a (const) char array and a string literal.