10

我有一个类来包装字符串文字并在编译时计算大小。

构造函数如下所示:

template< std::size_t N >
Literal( const char (&literal)[N] );

// used like this
Literal greet( "Hello World!" );
printf( "%s, length: %d", greet.c_str(), greet.size() );

但是代码有问题。以下代码编译,我想使它成为一个错误。

char broke[] = { 'a', 'b', 'c' };
Literal l( broke );

有没有办法限制构造函数,使其只接受 c 字符串文字?编译时检测是首选,但如果没有更好的方法,运行时是可以接受的。

4

6 回答 6

11

有一种方法可以强制使用字符串文字参数:制作用户定义的文字运算符。您可以使运算符constexpr在编译时获取大小:

constexpr Literal operator "" _suffix(char const* str, size_t len) {
    return Literal(chars, len);
}

目前我不知道有任何编译器实现了这个特性。

于 2011-09-30T17:08:43.650 回答
6

的。您可以使用以下预处理器生成编译时错误:

#define IS_STRING_LITERAL(X) "" X ""

如果您尝试传递字符串文字以外的任何内容,则编译将失败。用法:

Literal greet(IS_STRING_LITERAL("Hello World!"));  // ok
Literal greet(IS_STRING_LITERAL(broke)); // error
于 2011-09-30T17:47:00.093 回答
3

使用完全支持的 C++11 编译器,constexpr我们可以使用constexpr使用函数的构造constexpr函数,该函数编译为非常量表达式主体,以防不满足尾随零字符前提条件,导致编译失败并出现错误。以下代码扩展了 UncleBens 的代码,灵感来自Andrzej 的 C++ 博客中的一篇文章

#include <cstdlib>

class Literal
{
  public:

    template <std::size_t N> constexpr
    Literal(const char (&str)[N])
    : mStr(str),
      mLength(checkForTrailingZeroAndGetLength(str[N - 1], N))
    {
    }

    template <std::size_t N> Literal(char (&str)[N]) = delete;

  private:
    const char* mStr;
    std::size_t mLength;

    struct Not_a_CString_Exception{};

    constexpr static
    std::size_t checkForTrailingZeroAndGetLength(char ch, std::size_t sz)
    {
      return (ch) ? throw Not_a_CString_Exception() : (sz - 1);
    }
};

constexpr char broke[] = { 'a', 'b', 'c' };

//constexpr Literal lit = (broke); // causes compile time error
constexpr Literal bla = "bla"; // constructed at compile time

我用 gcc 4.8.2 测试了这段代码。使用 MS Visual C++ 2013 CTP 编译失败,因为它仍然不完全支持constexprconstexpr成员函数仍然不支持)。

也许我应该提到,我的第一个(也是首选)方法是简单地插入

static_assert(str[N - 1] == '\0', "Not a C string.")

在构造函数体内。它因编译错误而失败,似乎constexpr构造函数必须有一个空的主体。我不知道,这是否是 C++11 的限制以及未来的标准是否会放宽。

于 2014-03-30T13:44:56.067 回答
2

不,没有办法做到这一点。字符串文字具有特定的类型,并且所有方法重载解析都是在该类型上完成的,而不是字符串文字。任何接受字符串文字的方法最终都会接受任何具有相同类型的值。

如果您的函数绝对依赖于作为字符串文字的项目来运行,那么您可能需要重新访问该函数。这取决于它无法保证的数据。

于 2011-09-30T16:50:21.477 回答
0

字符串文字没有单独的类型来将其与 const char 数组区分开来。

但是,这会使意外传递(非常量)char 数组变得稍微困难​​一些。

#include <cstdlib>

struct Literal
{
    template< std::size_t N >
    Literal( const char (&literal)[N] ){}

    template< std::size_t N >
    Literal( char (&literal)[N] ) = delete;
};

int main()
{
    Literal greet( "Hello World!" );
    char a[] = "Hello world";
    Literal broke(a); //fails
}

至于运行时检查,非文字的唯一问题是它可能不是空终止的?正如您知道数组的大小一样,您可以遍历它(最好向后循环)以查看其中是否有 a \0

于 2011-09-30T18:37:08.517 回答
0

我曾经想出一个 C++98 版本,它使用类似于@k.st 提出的方法。为了完整起见,我将添加它以解决对 C++98 宏的一些批评。此版本试图通过防止通过私有 ctor 直接构造并将唯一可访问的工厂函数移动到详细名称空间中来强制执行良好的行为,该名称空间又由“官方”创建宏使用。不完全漂亮,但更傻一点。这样,如果用户想要行为不端,至少必须明确使用明显标记为内部的功能。与往常一样,没有办法防止故意恶意。

class StringLiteral
{
private:
    // Direct usage is forbidden. Use STRING_LITERAL() macro instead.
    friend StringLiteral detail::CreateStringLiteral(const char* str);
    explicit StringLiteral(const char* str) : m_string(str)
    {}

public:
    operator const char*() const { return m_string; }

private:
    const char* m_string;
};

namespace detail {

StringLiteral CreateStringLiteral(const char* str)
{
    return StringLiteral(str);
}

} // namespace detail

#define STRING_LITERAL_INTERNAL(a, b) detail::CreateStringLiteral(a##b)

/**
*   \brief The only way to create a \ref StringLiteral "StringLiteral" object.
*   This will not compile if used with anything that is not a string literal.
*/
#define STRING_LITERAL(str) STRING_LITERAL_INTERNAL(str, "")
于 2017-08-16T11:50:43.577 回答