11

假设我有这两个重载:

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

void Log(const std::wstring& message)
{
    // Do something
}

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

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

4

7 回答 7

14

所以这源于基思汤普森的回答......据我所知,您不能将字符串文字限制为仅限普通函数,但您可以将其用于宏函数(通过技巧)。

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

基本上,编译器将转换"abc" "def"为看起来与"abcdef". 同样,它将转换"" "abc""abc". 在这种情况下,您可以使用它来为您带来好处。


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

#define LOG(arg) do { static_assert(true, arg); Log(arg); } while (false)

在这里,我们使用 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)
于 2013-09-02T03:49:21.033 回答
8

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

定义一个宏,并使用#“字符串化”操作符来保证只有一个字符串字面量将被传递给函数(除非有人绕过宏并直接调用函数)。例如:

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

输出是:

Log: "hello world"
Log: hello world
Log: s

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

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

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

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

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

于 2013-09-01T22:52:46.083 回答
6

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

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

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

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

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

于 2013-09-01T22:48:26.627 回答
3

如果您Log改为定义为宏,并为文字和std::wstring处理调用单独的方法,则以下一些变体应该起作用:

#define Log(x) ((0[#x] == 'L' && 1[#x] == '"') ? LogLiteral(x) : LogString(x))

void
LogLiteral (const wchar_t *s) {
    //...do something
}

void
LogString (const std::wstring& s) {
    //...do something
}

诀窍是您需要相反的定义,LogLiteral()以便编译通过,但永远不应该调用它。

inline void LogLiteral (const std::wstring &s) {
    throw std::invalid_argument(__func__);
}

此代码为您提供了重载Log()方法的行为,因为您可以将字符串文字或非字符串文字传递给Log()宏,它最终将调用LogLiteral()LogString()。这提供了编译时验证,因为除了代码识别为字符串文字的内容之外,编译器不会将任何内容传递给对LogLiteral(). 在足够优化的情况下,可以删除条件分支,因为检查的每个实例都是静态的(在 GCC 上,它已被删除)。

于 2013-09-02T00:04:39.453 回答
2

这是我刚刚使用printf我在上面的评论中建议的 hack 制作的一个简单示例:

#include <cstdio>

#define LOG_MACRO(x) do { if (0) printf(x); Log(x); } while (0)

void Log(const char *message)
{
    // do something
}

void function(void)
{
    const char *s = "foo";
    LOG_MACRO(s);
    LOG_MACRO("bar");
}

用 Clang 编译这个的输出似乎正是您正在寻找的:

$ clang++ -c -o example.o example.cpp
example.cpp:13:15: warning: format string is not a string literal
      (potentially insecure) [-Wformat-security]
    LOG_MACRO(s);
              ^
example.cpp:3:41: note: expanded from macro 'LOG_MACRO'
#define LOG_MACRO(x) do { if (0) printf(x); Log(x); } while (0)
                                        ^
1 warning generated.

我确实不得不切换到printf而不是wprintf,因为后者似乎不会产生警告——不过我想这可能是一个 Clang 错误。

GCC 的输出类似:

$ g++ -c -o example.o example.cpp
example.cpp: In function ‘void function()’:
example.cpp:13: warning: format not a string literal and no format arguments
example.cpp:13: warning: format not a string literal and no format arguments

编辑:您可以在此处查看 Clang 错误。我刚刚添加了关于-Wformat-security.

于 2013-09-02T00:24:27.940 回答
2

我认为您不能强制只将字符串文字传递给函数,但文字是字符数组,您可以强制执行:

#include <iostream>

template<typename T>
void log(T) = delete; //Disable everything

template <std::size_t Size>
void log(const wchar_t (&message)[Size]) //... but const wchar_t arrays
{
    std::cout << "yay" << std::endl;
}

const wchar_t * get_str() { return L"meow"; }

int main() {
    log(L"foo"); //OK

    wchar_t arr[] = { 'b', 'a', 'r', '0' };
    log(arr); //Meh..

//    log(get_str()); //compile error
}

缺点是,如果您有一个运行时字符数组,它也可以工作,但不适用于通常的运行时 c 样式字符串。

但是,如果您可以使用稍微不同的语法,那么答案是肯定的:

#include <cstddef>
#include <iostream>

void operator"" _log ( const wchar_t* str, size_t size ) {
  std::cout << "yay" << std::endl;
}

int main() {
  L"Message"_log;
}

当然,这两种解决方案都需要兼容 C++11 的编译器(使用 G++ 4.7.3 测试的示例)。

于 2013-09-01T23:46:50.947 回答
0

添加此替代方案以供将来参考。它来自SO问题Is it possible to overload a function that can tell a fixed array from a pointer?

#include <iostream>
#include <type_traits>

template<typename T>
std::enable_if_t<std::is_pointer<T>::value>
foo(T)
{
    std::cout << "pointer\n";
}

template<typename T, unsigned sz>
void foo(T(&)[sz])
{
    std::cout << "array\n";
}

int main()
{
  char const* c = nullptr;
  char d[] = "qwerty";
  foo(c);
  foo(d);
  foo("hello");
}

上面的代码片段在http://webcompiler.cloudapp.net/上编译并运行良好

于 2015-01-28T11:28:15.057 回答