字符串字面量引用类

7
C++1y 中,我可以有一个绑定到字符串字面量但不是 char*char[]& 或类似的引用类吗?
class LitRf{
  const char* data_;
  size_t size_;
public:
  LitRf(const char* /*?*/ s) : data_{s},size_{sizeof(s)} { /*?*/ }
};

我想最好的方法是使用const char (&s)[N](带有template<size_t N>)作为参数类型。但它也绑定到除字符串字面值之外的任何const char数组。 - leemes
1
我知道。你不能改变这个。常量性总是可以添加,但不能删除。你只能添加一个非常量性的重载,然后=delete它。 - leemes
你真的是想用 sizeof(s) 吗?因为在你的情况下,这将返回1... 你可能想要使用 strlen(s) 或者如果 s 是一个数组,则使用 sizeof(s) - 1。即使在那种情况下,也不要使用 sizeof(s)。请注意,字符串结束符零包含在数组大小中,但不包括在字符串大小(strlen)中。 - leemes
@leemes sizeof通常是4(32位架构,CHAR_BIT == 8)或8(64位架构)用于指针,或者用于文字或数组的存储大小(包括终止0)。 我的意思是让我的size_存储存储大小(与您的答案完全相同),但从大小中减去或不减去1与问题关系不大。;-) - Petr Skocik
是的,我当然是指4 ;) - leemes
显示剩余8条评论
3个回答

3

C++11取消了唯一一种正式的检测字符串字面量的方式,即通过其隐式转换为非常量指针。

无论如何,使用一个小技巧就必须使用宏。

在C++11及以后,您所能做的最好的事情是建立一个强烈的约定,即一个const字符类型的数组是一个字面量。

即,在您的示例中,

class LitRf
{
private:
    const char* data_;
    Sz size_;

    template< size_t n >
    LitRf( char (&)[n] ) = delete;

public:
    template< size_t n >
    LitRf( char const (&s)[n] )
        : data_{s}, size_{sizeof(s) - 1}
    {}
};

请注意使用 size_t 而不是可能带符号的类型 Sz。这样可以确保代码可以在 g++ 上编译。不幸的是,该编译器或者它的早期版本存在一个错误,它对 size_t 非常坚持,否则它将拒绝接受代码。

谢谢。 char const [] ~ string literal 是一个合理的折衷方案。不幸的是,这也会从非const char数组进行初始化。(别管 Sz。我不知道它是什么 - 我在我的命名空间中将其用作std :: size_t的typedef) - Petr Skocik
但是您无法区分const char数组和charconst数组。 - Barry
为什么是 sizeof(s) - 1?我知道,这是因为字符串末尾的0,但是原来题目中没有减去它。 - leemes
啊,算了吧 - 我猜 OP 不是想做 sizeof(s),因为在他的情况下这个值是1 - 他可能是想用 strlen,这样确实可以匹配你的代码。 - leemes
添加了一个无法访问的构造函数并删除了它,以防止从非const char数组进行构造。 - Cheers and hth. - Alf

3
我想最好的做法是使用 const char (&s)[N] (带有 template<size_t N>) 作为参数类型。不过它也会绑定到除字符串字面值外的任何 const char 数组上。
添加一个被删除的非 const char 数组构造函数,以禁止使用非 const 数组调用它。
class LitRf
{
    const char* data_;
    Sz size_;
public:
    template<size_t N>
    LitRf(char const (&s)[N])
        : data_{s}, size_{N}
    {}

    template<size_t N>
    LitRf(char (&s)[N]) = delete;
};

除此之外,您可以使用宏包装器,这样(当构造函数从未在没有它的情况下使用时)只能通过文字构造对象,甚至不能通过变量构造。
#define MakeLitRf(s) LitRf(s "")

这个想法是将两个字符串字面值连接在一起,其中第二个字符串只是一个空字符串。只有第一个也是字符串字面值时才有可能实现这一点;如果放置一个变量,那么就会出现语法错误。经过宏展开后,编译器看到类似于LitRf("foo" "")的东西,它等同于LitRf("foo")。以下是一些例子:

auto x = MakeLitRf("foo");  // works

const char *foo = "foo";
auto x = MakeLitRf(foo);    // fails

auto x = LitRf(foo);        // works, but we want it to fail...

在最后一种情况下,用户无意中(或有意?)没有使用宏,使我们的工作变得毫无价值。为了让其也失败,可以向构造函数添加一个隐藏参数,该参数在直接调用时需要添加(当然,在宏的定义中也要添加)。
class LitRf
{
    const char* data_;
    Sz size_;
public:
    // Called by the macro MakeLitRf. Unlikely to be called directly unless the user knows what he's doing.
    LitRf(const char *s, void *)
        : data_{s}, size_{N}
    {}

    // Called without macro! Throw a compiler error, explaining what's wrong.
    LitRf(const char *s)
    {
        static_assert(false, "Please use the macro `MakeLitRf` with a string literal to construct a `LitRf`.");
    }
};

#define MakeLitRf(s) LitRf(s "", nullptr)

1
class LitRf{
  const char* data_;
  Sz size_;
public:
  LitRf(const char* /*?*/ s) : data_{s},size_{sizeof(s)} { /*?*/ }
  LitRf(char*) = delete;
};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接