C++:仅接受字符串文字的构造函数

发布于 2024-08-17 14:38:02 字数 1285 浏览 12 评论 0原文

是否可以创建一个接受字符串文字但不接受例如char const *的构造函数(或函数签名)?

是否可以有两个重载来区分字符串文字和 char const * ?

C++ 0x 会允许使用自定义后缀 - 但我正在寻找“更早的”解决方案。

基本原理:避免在以字符串文字形式给出时不会修改的字符串的堆复制。

这些字符串直接转至需要 const char * 的 API,无需任何处理。大多数调用确实使用不需要额外处理的文字,只有在少数情况下才会构造它们。我正在寻找保留本机调用行为的可能性。

注意: - 因为它出现在答案中:有问题的代码根本不使用 std::string,但一个很好的例子是:

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; }
}

结果:

(1) 这是做不到的。
(2)我意识到我什至不是在寻找文字,而是在寻找编译时常量(即“不需要复制的任何内容”)。

我可能会使用以下模式来代替:

const literal str_Ophelia = "Ophelia";

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

使用一个简单的

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

这并不能阻止任何人滥用它(我应该找到一个更好的名称......),但它允许所需的优化,但默认情况下保持安全。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

讽刺将军 2024-08-24 14:38:02

基于sbi想法的工作解决方案:

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
}

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
}
万水千山粽是情ミ 2024-08-24 14:38:02

是的,它可以做到!我想出了一个适用于 C++03 且没有包装类的解决方案(这会破坏 return 语句中的一些隐式转换)。

首先,您需要一个 const char (&)[N] 类型的构造函数模板,因为这是字符串文字的原始类型。然后,您还需要另一个用于 char (&)[N] 类型的类型(例如 char 缓冲区),这样它们就不会出现在文字的构造函数中。也许您还需要一个 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

现在的问题是,对于字符串文字,编译器仍然认为 const char* 构造函数是比模板实例更好的选择,因为数组到指针的转换具有完全匹配排名。 (13.3.3.1.1)

因此,技巧是降低 const char* 构造函数的优先级。这可以通过将其更改为模板并使用 SFINAE 仅将其与 const char* 类型进行匹配来完成。构造函数没有返回值,只有一个参数,这是类型推导所必需的。因此,需要另一个具有默认值的“虚拟参数”,它使用条件类型特征:templateFoo(T, 类型名称 IsCharPtr::Type=0)

解决方案:

#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;
}

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;
}
笨死的猪 2024-08-24 14:38:02

不,你不能这样做 - 字符串文字和 const char* 是可以互换的。一种解决方法可能是引入一个特殊的类来保存指向字符串文字的指针,并使构造函数仅接受该指针。这样,每当您需要传递文字时,您都可以调用该类的构造函数并传递临时对象。这并不能完全防止误用,但使代码更易于维护。

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.

如歌彻婉言 2024-08-24 14:38:02

如果您确切地知道编译器和平台如何处理字符串文字,则可能可以编写一个可以执行此操作的解决方案。如果您知道编译器始终将字符串文字放入特定的内存区域,则可以根据该内存的边界检查指针。如果它落在该块内,则您将获得一个字符串文字;否则,您将在堆或堆栈上存储一个字符串。

然而,这个解决方案将是特定于平台/编译器的。它不便于携带。

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.

摘星┃星的人 2024-08-24 14:38:02

这是基于 ansiwen答案

请注意,就像那个答案一样,这种方法并不完美。代码中的最后一个示例将被解释为文字,即使它们不是文字。因此存在UB的风险。

但应该很少见,因为没有太多理由创建 const char 数组,因为它比字符串文字没有什么好处。我们的代码中没有这种情况。

#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;
}

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;
}
坠似风落 2024-08-24 14:38:02

在某些平台上,我必须将字符串文字声明为static const char *,以便程序能够访问只读内存中的文本。当声明为 const char * 时,汇编列表显示文本已从 ROM 复制到堆栈变量中。

不必担心接收者,也许可以尝试使用 static const char * 声明字符串文字。

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 *.

染墨丶若流云 2024-08-24 14:38:02

使用 C++14 中新的用户定义文字(对于 Clang 3.5 - 它也适用于 C++11),有一个优雅的解决方案:

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);
}

它可以像这样使用:

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

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

有一个缺点:你必须追加_L 到每个文字 - 但这并不是什么大问题,真的。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文