【问题标题】:Restrict passed parameter to a string literal将传递的参数限制为字符串文字
【发布时间】:2017-11-29 09:06:46
【问题描述】:

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

构造函数如下所示:

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

【问题讨论】:

  • @Nawaz 特别提到了 C 字符串文字的问题。我认为这样提及是恰当的。
  • c 标签可能不合适。走着瞧。我希望有人会使用 c++11 的新功能(constexpr、可变参数等)来解决这个问题。
  • 只在运行时设置大小会不会损失太大?尽管如此,我还是会发布一个答案。
  • @JaredPar:C 字符串文字不仅是 C 字符串文字,它们也是 C++ 字符串文字。事实上,没有什么叫做 C 字符串文字。只有字符串文字,碰巧在两种语言中都有!
  • @Nawaz 我意识到 c 风格的字符串文字在两种语言中都可用。鉴于 C++11 标记和明确提及 c 样式字符串文字,我认为 C++11 中可能有一个与 c 样式字符串不同的新功能(对于 C++11 功能列表不是最新的) ) 因此我添加了标签。它已被删除,所以不用担心。

标签: c++ c++11 string-literals


【解决方案1】:

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

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

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

【讨论】:

  • 这个运算符不允许"Hello world"_literal,虽然:(
  • @JohannesSchaub-litb 哦 :( 你是对的。它仅适用于整数和浮点文字。很遗憾。我要留下答案,但现在不那么酷了 :(
  • @Johannes,使用当前的 C++ 标准有一种更简单的方法。
  • @deft_code UD 文字运算符可以是constexprHereconstexpr 的 GCC 支持有多好(使用 4.7 的快照编译得很好),所以只要支持 UD 文字,您的问题就基本上得到了解决。
  • 为了进一步实验,这里是@Luc 没有宏的代码(但它不再是对 UD 文字函数的模拟):ideone.com/JF4cx
【解决方案2】:

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

#define IS_STRING_LITERAL(X) "" X ""

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

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

【讨论】:

  • 这个解决方案取决于每个人都是好公民,而问题的全部目的是阻止人们成为坏公民。
  • @JohannesSchaub-litb,实际上,我故意输入"" X 格式(这会产生更好的编译器错误)。我认为X "" 解决了这个问题。已编辑
  • IS_STRING_LITERAL(+ (std::string)).
  • 赞成。这正是我一直在寻找的,因为我们正在使用某些不符合 c++11 的编译器。谢谢!
【解决方案3】:

使用完全支持constexpr 的C++11 编译器,我们可以使用constexpr 构造函数使用constexpr 函数,如果尾随零字符前置条件不是,它会编译为非常量表达式主体已完成,导致编译失败并出现错误。以下代码扩展了UncleBens的代码,灵感来自an article of Andrzej's C++ blog

#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] == '\0', "Not a C string.")

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

【讨论】:

    【解决方案4】:

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

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

    【讨论】:

    • @iammilind 你的方法取决于每个来电者都是好公民。这个问题的全部目的是阻止人们成为坏公民。
    【解决方案5】:

    字符串文字没有单独的类型来将其与 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
    }
    

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

    【讨论】:

    • 另一个潜在问题是文字具有静态存储持续时间,但非文字可能具有更短的持续时间。如果目标是避免不必要的复制,则参数可能会被保存,并可能成为非文字的悬空指针。 (很抱歉挖掘了一个旧答案。)
    【解决方案6】:

    我曾经想出一个 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, "")
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-03
      • 1970-01-01
      • 2021-09-02
      • 1970-01-01
      • 2013-07-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多