如果字符串参数不是字符串文字,是否可以强制编译错误?

Joh*_*ell 10 c++ templates

假设我有这两个重载:

void Log(const wchar_t* message)
{
    // Do something
}

void Log(const std::wstring& message)
{
    // Do something
}
Run Code Online (Sandbox Code Playgroud)

我可以在第一个函数中添加一些编译时验证,传递的参数是字符串文字吗?

编辑:澄清为什么这对我来说是好的; 我当前的高频日志记录使用字符串文字,因此在存在非堆分配保证时可以进行大量优化.第二次重载今天不存在,但我可能想添加它,但后来我想保留第一个用于极端场景.:)

Bil*_*nch 12

所以这是基于Keith Thompson的答案 ...据我所知,你不能将字符串文字限制为只有普通函数,但你可以将其用于宏函数(通过技巧).

#include <iostream>
#define LOG(arg) Log(L"" arg)

void Log(const wchar_t *message) {
    std::wcout << "Log: " << message << "\n";
}

int main() {
    const wchar_t *s = L"Not this message";
    LOG(L"hello world");  // works
    LOG(s);               // terrible looking compiler error
}
Run Code Online (Sandbox Code Playgroud)

基本上,编译器将转换"abc" "def"为完全一样"abcdef".同样,它将转换"" "abc""abc".在这种情况下,您可以使用此功能.


我也在C++ Lounge上看到了这个评论,这让我对如何做到这一点给出了另一个想法,它提供了一个更清晰的错误信息:

#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)
Run Code Online (Sandbox Code Playgroud)

在这里,我们使用static_assert需要字符串文字作为第二个参数的事实.如果我们传递一个变量,我们得到的错误也很好:

foo.cc:12:9: error: expected string literal
    LOG(s);
        ^
foo.cc:3:43: note: expanded from macro 'LOG'
#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)
Run Code Online (Sandbox Code Playgroud)


Kei*_*son 8

我相信你的问题的答案是否定的 - 但这是一种做类似事情的方法.

定义一个宏,并使用#"stringification"运算符来保证只有一个字符串文字将被传递给该函数(除非有人绕过宏并直接调用该函数).例如:

#include <iostream>

#define LOG(arg) Log(#arg)

void Log(const char *message) {
    std::cout << "Log: " << message << "\n";
}

int main() {
    const char *s = "Not this message";
    LOG("hello world");
    LOG(hello world);
    LOG(s);
}
Run Code Online (Sandbox Code Playgroud)

输出是:

Log: "hello world"
Log: hello world
Log: s
Run Code Online (Sandbox Code Playgroud)

传递s给的尝试LOG()没有触发编译时诊断,但它没有将该指针传递给该Log函数.

这种方法至少有两个缺点.

一个是它很容易被绕过; 您可以通过在源代码中搜索对实际函数名称的引用来避免这种情况.

另一个是字符串文字的字符串化不只是给你相同的字符串文字; 字符串化的版本"hello, world""\"hello, world\"".我想你的Log函数可以删除"传递的字符串中的任何字符.您可能还想处理反斜杠转义; 例如,"\n"(包含换行符的1个字符的字符串)被字符串化为"\\n"(包含反斜杠和字母的2个字符的字符串n).

但我认为更好的方法是不要依赖编译器来诊断带有字符串文字之外的参数的调用.只需使用其他工具扫描源代码以调用Log函数,并报告第一个参数不是字符串文字的任何调用.如果你可以强制执行的调用特定的布局(例如令牌Log,(和一个字符串在同一行文字),这应该不会太困难.

  • 你也可以这样做:`#define LOG(arg)Log(""arg)`,它会强制`arg`成为一个带引号的字符串,否则就会失败(带有可怕的错误信息). (5认同)

Die*_*ühl 5

您无法直接检测字符串文字,但可以检测参数是否是非常接近的字符数组.但是,你不能从内部做到,你需要从外面做:

template <std::size_t Size>
void Log(wchar_t const (&message)[Size]) {
    // the message is probably a string literal
    Log(static_cast<wchar_t const*>(message);
}
Run Code Online (Sandbox Code Playgroud)

上面的函数将处理宽字符串文字和宽字符数组:

Log(L"literal as demanded");
wchar_t non_literal[] = { "this is not a literal" };
Log(non_literal); // will still call the array version
Run Code Online (Sandbox Code Playgroud)

请注意,关于作为文字的字符串的信息并不像人们希望的那样有用.我经常认为可以使用这些信息来避免计算字符串长度,但不幸的是,字符串文字仍然可以嵌入空字符,这会扰乱字符串长度的静态演绎.

  • 为什么嵌入null搞乱静态检测长度?编译器对数组大小的预知总是准确无误.真正的问题是为什么有人想要区分字符串文字和非文字常量大小的数组. (3认同)