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

pet*_*hen 20 c++ constructor overloading string-literals

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

是否有可能有两个可以区分字符串文字的重载char const *

C++ 0x会允许使用自定义后缀 - 但我正在寻找"早期"解决方案.

基本原理:避免在作为字符串文字给出时不会被修改的字符串的堆副本.

这些字符串直接转到API,期望const char *没有任何处理.大多数调用确实使用不需要额外处理的文字,只在少数情况下构建它们.我正在寻找保留原生呼叫行为的可能性.

注意: - 因为它出现在答案中:有问题的代码根本不使用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; }
}
Run Code Online (Sandbox Code Playgroud)

结果:

(1)不能做到.
(2)我意识到我甚至不是在寻找一个文字,而是一个编译时常量(即"任何不需要复制的东西").

我可能会使用以下模式:

const literal str_Ophelia = "Ophelia";

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

用一个简单的

struct literal  
{ 
   char const * data; 
   literal(char const * p) : data(p) {} 
   operator const char *() const { return data; }
};
Run Code Online (Sandbox Code Playgroud)

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

Kir*_*sky 19

基于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
}
Run Code Online (Sandbox Code Playgroud)

  • 在C++ 03中,你可以通过检查它们是否转换为`char*`和`const char []来保持它们分开:如果它们这样做,它是一个字符串文字,如果它们没有,它就是一个普通的数组.但是在C++ 0x中,他们将不推荐的转换从字符串文字删除到`char*`,因此这个hack将不再起作用了:) (5认同)
  • @Downvoter:关注评论?你不同意哪一点? (2认同)

ans*_*wen 17

是的,它可以做到!我想出了一个适用于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
Run Code Online (Sandbox Code Playgroud)

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

因此,诀窍是降低const char*构造函数的优先级.这可以通过将其更改为模板并使用SFINAE仅根据类型进行匹配来完成const char*.构造函数没有返回值,只有一个参数,这是类型推导所必需的.因此,需要具有默认值的另一个"虚拟参数",它使用条件类型特征:template<typename T> Foo(T, typename IsCharPtr<T>::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;
}
Run Code Online (Sandbox Code Playgroud)


sha*_*oth 9

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


小智 1

这是基于ansiwen回答为 C++20 更新的版本。

请注意,就像那个答案一样,这种方法并不完美。代码中的最后一个示例将被解释为文字,即使它们不是文字。因此存在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;
}
Run Code Online (Sandbox Code Playgroud)