【问题标题】:Using templates to get printf() to work with std::string使用模板让 printf() 与 std::string 一起工作
【发布时间】:2018-02-27 00:47:44
【问题描述】:

我想生成一个printf 版本,它会自动将其std::string 参数转换为其c_str() 值。 (我发现printf 语法比 C++ 流更简洁、更简单)。

我想用一些简单的东西。简洁明了是唯一的设计目标。 Boost有类似的东西,但它太复杂了。我根本不关心效率或避免复制。

下面是一个简单的示例,几乎可以使用一个参数。代码有两个问题:

(1) 如何将其扩展到任意数量(或者说至少 3 个)参数?我知道我可以使用可变参数模板,但我不明白如何在这里使用它们。

(2) 当格式参数const char* 与要打印的实际对象之间存在类型不匹配时,如何保持编译器警告(在clang 和-Wall 下)?

编辑:第 (2) 部分几乎已解决。我在myprint() 之前添加了__attribute__((format printf,1,2))。这会进行类型检查,但要求 myprint 是可变的。

示例代码:

#include <stdio.h>
#include <string>
using std::string;
template <typename T>
void myprint(const char* format, T arg){
  printf(format, arg);
}

template <>
void myprint(const char* format, string arg){
  printf(format,(arg+" STRING ").c_str());
}

int main(){
  string x("foo");
  myprint ("The value of 1 is: %s\n", "simple"); //works
  myprint ("The value of 2 is: %s\n", x); // works
  myprint ("The value of 2 is: %d\n", x); // fails - no warning!
  printf ("The value of 2 is: %d\n", x.c_str()); // works - warning
  return 0;
}

【问题讨论】:

  • 如果格式字符串说明符与相应的参数不匹配,printf() 给出未定义的行为(例如,在提供 const char * 时指定 %d)。您对 myprint() 的第三次调用也是如此 - 当相应的参数是 std::string 时指定 %d,您的代码会将其转换为具有 %d 格式和 const char * 的 printf() 调用。在模板被实例化之前,编译器不能在模板代码中给出类似的警告。
  • 无论如何,通过 google,您将能够找到在 C++ 中使用可变参数模板的类型安全 printf() 的示例。
  • @Peter - 他的抱怨是,一旦通话结束,警告就不会出现。我怀疑模板代码的某些警告已关闭...? (但我对 Clang 的了解还不够好,这只是一个疯狂的猜测)
  • printf() 的编译器警告依赖于编译器扫描格式字符串,并将说明符与参数类型进行比较。编译器不能使用printf() 来处理任意格式的字符串(例如,在运行时生成的字符串),并且不会倾向于在不使用编译器特定扩展的情况下对接受const char * 的任意函数执行此操作。
  • @kdog:编译时唯一可用的格式字符串在您的main() 函数中。在您实际调用printf() 的地方,格式字符串作为变量传递,其值在运行时才知道,因此编译器无法验证printf() 调用。但是,您可以让编译器在 main() 中验证您的 mystring() 函数调用,您将在其中为格式字符串传递字符串文字。用format attribute 标记myprintf(),这样编译器就知道你想要这种行为。

标签: c++ templates clang variadic-templates


【解决方案1】:

问题是您使用的是 C 风格的 printf 说明符,而不考虑您传递给它们的数据类型。您不能将 std::string 或 char char* 传递给 %d 说明符。这正是 C++ 流避免的错误类型。

如果您希望您的编译器在编译时验证您的 myprint() 函数的 printf 样式参数,您需要相应地标记该函数。 GCC 和 Clang 有一个 format 属性用于此目的(请参阅 clang documentation 关于此)。

否则,要真正以类型安全的方式使用printf(),您应该使您的myprint() 函数使用C++11 可变参数模板参数,并让模板参数决定您传递给@987654330 的说明符和数据@ 每个参数。如果您环顾四周,网上有很多使用可变参数模板的“安全” printf 实现。

【讨论】:

  • 不一定是类型安全的,但为什么不能给出与 printf() 相同的警告?也就是说,当模板外部存在类型不匹配时, printf() 会发出警告。为什么它不能在模板内发出警告?
  • 模板本身是否有一些简单、简短的方法来检查其格式字符串是否是类型安全的,假设格式字符串只使用%d、%s、%p 而没有否则使用%?
  • 可能使用 constexpr 字符串自省。不过,这不是一项小工作。
  • @kdog: "printf() 在模板外出现类型不匹配时发出警告" - 这是编译器特定的扩展。并非所有 C++ 编译器都会在编译时验证 printf 样式参数。 “模板本身是否有一些简单、简短的方法来检查其格式字符串是否是类型安全的” - 请参阅stackoverflow.com/a/17672820/65863
  • 我只关心 clang 中的警告。但是代码本身当然应该在clang之外编译。 (clang 是开发机器,但部署到许多其他平台)
【解决方案2】:

这里有一个想法:

namespace io {
template <class X>
using is_string = typename std::enable_if<std::is_same<X, std::string>::value>::type;

template <class X>
using is_not_string = typename std::enable_if<!std::is_same<X, std::string>::value>::type;

template <class T, class = is_not_string<T>>
constexpr T forward_or_transform(T t) {
    return std::forward<T>(t); 
}

template <class T, class = is_string<T>>
constexpr const char* forward_or_transform(T t) { 
    return t.c_str(); 
}

template <class ...Ti>
int printf(const std::string& format, Ti...t) {
    return std::printf(format.c_str(), forward_or_transform(t)...);
}
}

auto s = std::string("const std::string&");
io::printf("[%s]-[%s]-[%s]-[%d]\n", std::string("std::string&&"), "const char *", s, 42);

打印:[std::string&amp;&amp;]-[const char *]-[const std::string&amp;]-[42]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-11
    • 2014-03-29
    • 2011-09-06
    • 1970-01-01
    • 1970-01-01
    • 2018-06-25
    相关资源
    最近更新 更多