Search code examples
c++constructoroverloadingstring-literals

C++: Constructor accepting only a string literal


Is it possible to create a constructor (or function signature, for that matter) that only accepts a string literal, but not an e.g. char const *?

Is it possible to have two overloads that can distinguish between string literals and char const *?

C++ 0x would kind-of allow this with a custom suffix - but I'm looking for an "earlier" solution.

Rationale: avoiding heap copy of strings that won't be modified when given as string literals.

These strings directly go to an API expecting a const char * without any processing. Most calls do use literals requiring no additional processing, only in a few cases they are constructed. I am looking for a possibility to preserve the native call behavior.

Note: - since it comes up in the answers: the code in question does not use std::string at all, but a good example would be:

class foo
{
   std::string m_str;
   char const * m_cstr;      
 public:
   foo(<string literal> s) : m_cstr(p) {}
   foo(char const * s) : m_str(s) { m_cstr = s.c_str(); }
   foo(std::string const & s) : m_str(s) { m_cstr = s.c_str(); }

   operator char const *() const { return m_cstr; }
}

Results:

(1) it can't be done.
(2) I realized I am not even looking for a literal, but for a compile-time-constant (i.e. "anything that needs not be copied").

I will probably use the following pattern instead:

const literal str_Ophelia = "Ophelia";

void Foo()
{
  Hamlet(str_Ophelia, ...);  // can receive literal or string or const char *
}

with a simple

struct literal  
{ 
   char const * data; 
   literal(char const * p) : data(p) {} 
   operator const char *() const { return data; }
};

That doesn't stop anyone from abusing it (I should find a better name...), but it allows the required optimization but remains safe by default.


Solution

  • Here is a version updated for C++20 based on ansiwen's answer.

    Note that just like that answer this method isn't perfect. The last examples in the code will be interpreted as literals even though they are not. So runs the risk of UB.

    But should be pretty rare since there's not much reason to create a const char array as that has few benefits over a string literal. We had no cases of this in our code.

    #include <iostream>
    
    #define LITERAL std::cout << "literal" << std::endl
    #define NON_LITERAL std::cout << "char array" << std::endl
    
    template <typename T>
    concept IsCharStar = std::is_same_v<T, char*> || std::is_same_v<T, const char*>;
    
    struct Foo {
        template<int N> Foo(const char (&)[N]) { LITERAL; }
        template<int N> Foo(char (&)[N]) { NON_LITERAL; }
        template<int N> Foo(const char (&&)[N]) { NON_LITERAL; }
        template<int N> Foo(char (&&)[N]) { NON_LITERAL; }
        template<IsCharStar T> Foo(T) { NON_LITERAL; }
    };
    
    char gchararray[] = "x";
    const char* gcharstar = "x";
    Foo f1() { return "hello"; }
    Foo f2() { return gchararray; }
    Foo f3() { return gcharstar; }
    Foo f4() { char buffer[10] = "lkj"; return buffer; }
    Foo f5() { const char buffer[10] = "lkj"; return buffer; }
    
    int main() {
        char buf[10] = "buffer";
        char* charstar = "char star";
        Foo v1("x");       // literal
        Foo v2(buf);       // char array
        Foo v3(charstar);  // char array
        f1();              // literal
        f2();              // char array
        f3();              // char array
        f4();              // char array
    
        // But note that it's not perfect
        const char buf2[15] = "const buffer";
        Foo v4(buf2);      // literal
        f5();              // literal, will result UB
        return 0;
    }