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

C++ 将传递的参数限制为字符串文字,c++,c++11,string-literals,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() ); 有没有办法限制构造函数,使其只接受c字符串文字?编译时检测是首选,

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

构造函数如下所示:

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() );

有没有办法限制构造函数,使其只接受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

字符串文字没有单独的类型来区分它与常量字符数组

但是,这会使意外传递(非常量)字符数组稍微困难一些

#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
}
#包括
结构文字
{
模板
文字(常量字符(&Literal)[N]){
模板
文字(字符和文字)[N])=删除;
};
int main()
{
文字问候(“你好,世界!”);
字符a[]=“你好,世界”;
(a);//失败
}

至于运行时检查,非文本的唯一问题是它不能以null结尾?正如您所知,数组的大小,您可以在它上面循环(最好是向后循环),以查看其中是否有
\0

使用完全支持
constepr
的C++11编译器,我们可以使用
constepr
函数使用
constepr
构造函数,如果未满足尾随零字符的先决条件,将编译为非常量表达式体,从而导致编译失败并出现错误。以下代码扩展了UncleBens的代码,其灵感来自:


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

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

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

@Nawaz问题特别提到了C字符串文字。我认为这样说是合适的。c标签可能不合适。我们拭目以待。我希望有人能用c++11的新特性(
constexpr
、variadics等)来解决这个问题。仅仅在运行时使用这个大小会不会太大损失?尽管如此,我还是会给出一个答案。@JaredPar:C字符串文字不仅仅是C字符串文字,它们也是C++-字符串文字。事实上,没有所谓的C字符串文字。只有两种语言的字符串文字@Nawaz我意识到c风格的字符串文字在两种语言中都可用。考虑到C++11标记和C风格字符串文本的明确提及,我认为C++11中可能有一个新功能,它不同于C风格字符串(与C++11功能列表不太一致),因此我添加了标记。它已经被删除了,所以不用担心。这个操作符不允许使用
“Hello world”\u literal
,但是:(@johanneschaub litb-oh:(你说得对。它只适用于整数和浮点文本。这很遗憾。我留下了答案,但现在它没有那么酷了:(Johannes)有一个更简单的方法来处理这个问题,用当前的C++标准。@ Debug代码UD文字操作符被允许是代码> CONTXPROP<代码>。GCC对<代码> CONTXPRP</代码>的支持是多么好(这是一个快照4.7的精简)。,所以只要支持UD文本,您的问题就基本上得到了解决。为了进一步试验,@Luc的代码不带宏(这不再是UD文本函数的模拟):@iammilind你的方法取决于每个打电话的人都是好公民。这个问题的全部目的是阻止人们成为坏公民。这个解决方案取决于每个人都是好公民,而问题的全部目的是阻止人们成为坏公民。@johanneschaub litb,事实上,我故意把
“X
格式(这会产生更好的编译器错误)。我认为
X”“
解决了这个问题。编辑的
是字符串+文字(+(std::STRING))
.Upvoted。这正是我想要的,因为我们正在使用某些不符合c++11的编译器。谢谢!另一个潜在的问题是,文本具有静态存储持续时间,但非文本的持续时间可能更短。如果目标是避免不必要的复制,则可能会保存参数并将其保存为e非文字的悬空指针。(为挖掘旧答案而道歉。)
#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
static_assert(str[N - 1] == '\0', "Not a C string.")
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, "")