开发者

C++: Constructor accepting only a string literal

开发者 https://www.devze.com 2022-12-16 13:30 出处:网络
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 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.


Working solution based on sbi idea:

struct char_wrapper
{
    char_wrapper(const char* val) : val(val) {};
    const char* val;
};

class MyClass {
public:
  template< std::size_t N >
  explicit MyClass(const char (&str)[N])
  {
      cout << "LITERAL" << endl;
  }
  template< std::size_t N >
  explicit MyClass(char (&str)[N])
  {
      cout << "pointer" << endl;
  }    
  MyClass(char_wrapper m)
  {
     cout << "pointer" << endl;
  }
};

int main()
{
    MyClass z("TEST1");     // LITERAL
    const char* b = "fff";
    MyClass a(b);           // pointer
    char tmp[256]; 
    strcpy(tmp, "hello"); 
    MyClass c(tmp);         // pointer
}


Yes, it can be done! I came up with a solution that works with C++03 and without a wrapper class (which breaks some implicit conversions in return statements).

First of all, you need a constructor template for the types const char (&)[N], since this is the original type of string literals. Then you also need another one for the types char (&)[N] - like char buffers for instance - so that they con't end up in the constructor for literals. And probably you also want a normal constructor for the type const char*.

template<int N> Foo(const char (&)[N]); // for string literals
template<int N> Foo(char (&)[N]);       // for non-const char arrays like buffers
Foo(const char*);                       // normal c strings

The problem now is, that for string literals the compiler still thinks, that the const char* constructor is a better choice than a template instance, since array-to-pointer conversions have exact-match rank. (13.3.3.1.1)

So, the trick is to lower the precedence of the const char* constructor. This can be done by changing it to a template as well and using SFINAE to match it only against the type const char*. The constructor has no return value and only one parameter, that is necessary for type deduction. Therefore another "dummy parameter" with default value is necessary, that uses the conditional type trait: template<typename T> Foo(T, typename IsCharPtr<T>::Type=0)

Solution:

#include <iostream>

#define BARK std::cout << __PRETTY_FUNCTION__ << std::endl

struct Dummy {};
template<typename T> struct IsCharPtr {};
template<> struct IsCharPtr<const char *> { typedef Dummy* Type; };
template<> struct IsCharPtr<char *> { typedef Dummy* Type; };

struct Foo {
  template<int N> Foo(const char (&)[N]) { BARK; }
  template<int N> Foo(char (&)[N]) { BARK; }
  template<typename T> Foo(T, typename IsCharPtr<T>::Type=0) { BARK; }
};

const char a[] = "x";
const char* b = "x";
const char* f() { return b; }

int main() {
  char buffer[10] = "lkj";
  char* c = buffer;
  Foo l("x");     // Foo::Foo(const char (&)[N]) [N = 2]
  Foo aa(a);      // Foo::Foo(const char (&)[N]) [N = 2]
  Foo bb(b);      // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *]
  Foo cc(c);      // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = char *]
  Foo ee(buffer); // Foo::Foo(char (&)[N]) [N = 10]
  Foo ff(f());    // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *]
  return 0;
}


No, you just can't do this - string literals and const char* are interchangeable. One workaround could be to introduce a special class to hold pointers to string literals and make a constructor only accepting that. This way whenever you need to pass a literal you call a constructor of that class and pass the temporary object. This doesn't completely prevent misuse, but makes code much more maintainable.


If you know exactly how your compiler and platform deal with string literals, it might be possible to write a solution that can do this. If you know that your compiler always puts string literals into a specific region of memory, you can check the pointer against the bounds of that memory. If it falls within that block, you've got a string literal; otherwise you've got a string stored on the heap or stack.

However, this solution would be platform/compiler-specific. It would not be portable.


On some platforms, I have had to declare string literals as static const char * in order for the program to access the text from Read-Only Memory. When declared as const char *, the assembly listing showed that the text was copied from ROM onto a stack variable.

Instead of worrying about the receiver, perhaps try declaring the string literals with static const char *.


With a new user-defined literals in C++14 (as for Clang 3.5 - it works with C++11 too), there is an elegant solution:

class Literal {
 public:
  explicit Literal(const char* literal) : literal_(literal) {}
  // The constructor is public to allow explicit conversion of external string
  // literals to `_L` literals. If there is no such need, then move constructor
  // to private section.

  operator const char* () { return literal_; }

 private:
  friend Literal operator"" _L (const char*, unsigned long);
  // Helps, when constructor is moved to private section.

  const char* literal_;
};

Literal operator"" _L (const char* str, unsigned long) {
  return Literal(str);
}

It may be used like this:

void f1(Literal) {}  // Accepts literals only.

int main() {
  auto str1 = "OMG! Teh Rey!"_L;
  std::cout << str1 << std::endl;
  f(str1);
}

There is one drawback: you have to append _L to every literal - but it's not a big deal, really.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号