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

时间:2011-09-30 16:47:23

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

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

构造函数如下所示:

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

6 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:4)

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

#define IS_STRING_LITERAL(X) "" X ""

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

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

答案 2 :(得分:3)

使用完全支持constexpr的C ++ 11编译器,我们可以使用constexpr构造函数使用constexpr函数,该函数编译为非const表达式主体,以防不满足尾随零字符前提条件,导致编译失败并显示错误。以下代码扩展了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进行编译失败,因为它仍然不完全支持constexprconstexpr成员函数仍然不受支持)。

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

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分: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
}

对于运行时检查,非文字的唯一问题是它可能不是以null结尾的?如你所知,数组的大小,你可以循环它(最好向后),看看它是否有\0

答案 5 :(得分: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, "")