将传递的参数限制为字符串字面值

Restrict passed parameter to a string literal

本文关键字:字符串 字面值 参数      更新时间:2023-10-16

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

构造函数如下:

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字符串字面量?编译时检测是首选,但如果没有更好的方法,运行时检测也是可以接受的。

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

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

目前我还不知道有哪个编译器实现了这个特性

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

#define IS_STRING_LITERAL(X) "" X ""

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

Literal greet(IS_STRING_LITERAL("Hello World!"));  // ok
Literal greet(IS_STRING_LITERAL(broke)); // error

对于完全支持constexpr的c++ 11编译器,我们可以使用constexpr构造器使用constexpr函数,如果没有满足末尾零字符的前提条件,则编译成非const表达式体,导致编译失败并出现错误。下面的代码扩展了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编译失败,因为它仍然不完全支持constexpr (constexpr成员函数仍然不支持)。 也许我应该提到,我的第一个(也是首选的)方法是简单地插入
static_assert(str[N - 1] == '', "Not a C string.")
构造函数体中的

。它失败了一个编译错误,看起来,constexpr构造函数必须有一个空的体。我不知道这是否是c++ 11的限制,也不知道未来的标准是否会放宽。

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

如果你的函数完全依赖于一个字符串字面值的项来运行,那么你可能需要重新访问这个函数。这取决于它无法保证的数据。

字符串字面值没有单独的类型来区分它与const char数组。

然而,这会使意外传递(非const)字符数组变得稍微困难一些。

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

至于运行时检查,非文字的唯一问题是它可能不是以空结束的?由于您知道数组的大小,您可以遍历它(最好是向后)以查看其中是否存在

我曾经设计过一个c++ 98版本,它使用了一种类似于@k.st提出的方法。为了完整起见,我将添加这一点,以解决对c++ 98宏的一些批评。这个版本试图强制良好的行为,防止通过私有域直接构造,并将唯一可访问的工厂函数移动到一个细节命名空间中,而这个命名空间又被"官方"创建宏使用。不是很漂亮,但更防傻瓜。这样,如果用户想要进行不当行为,他们至少必须显式地使用明显标记为内部的功能。一如既往,没有办法防止蓄意的恶意。

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, "")