String 可变模板函数(printf)的函数模板专门化

String 可变模板函数(printf)的函数模板专门化,string,c++11,variadic-templates,arduino-c++,String,C++11,Variadic Templates,Arduino C++,我有一些Arduino C++11代码,我正在努力改进:尝试使一个类似printf的函数专门处理String,这样我就不必在任何地方调用C_str()。基本上,对于任何内置类型,如int float bool等,我只想按原样传递arg,对于String,传递并返回c_str()。遇到了一些障碍,所以我在一些可用的在线编译器中尝试了这一点。起点是,使用std::string而不是string: #include <string> class SerialOut { public:

我有一些Arduino C++11代码,我正在努力改进:尝试使一个类似printf的函数专门处理String,这样我就不必在任何地方调用C_str()。基本上,对于任何内置类型,如int float bool等,我只想按原样传递arg,对于String,传递并返回c_str()。遇到了一些障碍,所以我在一些可用的在线编译器中尝试了这一点。起点是,使用std::string而不是string:

#include <string>

class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, args...);
    }
};

int main() {
    std::string greeting("hi");
    SerialOut::error("Message %d %s\n", 1, greeting.c_str());
}
#包括
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(消息、参数…);
}
};
int main(){
字符串问候语(“hi”);
SerialOut::错误(“消息%d%s\n”,1,greeting.c_str());
}
因此,我尝试创建一个函数模板,该模板只返回它得到的值,并对std::string进行专门化:

#include <string>

template <typename T, typename R=T> R raw(T& x) {return x;}
template <> const char* raw<>(std::string& x) {return x.c_str();}

class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, raw(args)...);
    }
};

int main() {
    std::string greeting("hi");
    SerialOut::error("Message %d %s\n", 1, greeting);
}
#包括
模板R原始(T&x){return x;}
template const char*raw(std::string&x){return x.c_str();}
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(味精、原料(args)…);
}
};
int main(){
字符串问候语(“hi”);
SerialOut::错误(“消息%d%s\n”,1,问候语);
}
在中运行此操作时出现编译错误:

clang版本7.0.0-3~ubuntu0.18.04.1(标签/发行版\u 700/最终版)
 clang++-7-pthread-std=c++11-o main.cpp
main.cpp:10:25:错误:无法传递非平凡类型的对象
通过变量函数的“std::_cxx11::基本_字符串”;呼叫将在结束时中止
运行时[-Wnon-pod-varargs]
printf(味精、原料(args)…);
^
main.cpp:16:20:注意:在函数模板专门化的实例化中
此处请求了“SerialOut::error”
SerialOut::错误(“消息%d%s\n”,1,问候语);
^
生成1个错误。
编译器退出状态1
没有错误,但未选择raw()专门化,因此问候语的输出是垃圾

在ArduinoIDE中,我得到了一个稍微不同的错误(当然,在用字符串替换std::string之后):

sketch\mqtt.cpp.o:在函数'char const*raw(String&')中:
sketch/utils.h:15:char const*raw(String&)的多重定义
sketch\Thermistor.cpp.o:sketch/utils.h:15:首先在此处定义
sketch\sketch.ino.cpp.o:在函数“char const*raw(String&)”中:
sketch/utils.h:15:char const*raw(String&)的多重定义
sketch\Thermistor.cpp.o:sketch/utils.h:15:首先在此处定义
我尝试了
raw()
函数的几种变体,但都没有成功。我想我只是缺少了一个微妙之处,或者说在C++11中不可能做到这一点


更新:我发现,其中一个答案在C++14中解决了上述问题(基本上使用
decltype(auto)
和重载而不是专门化)。我在它上面添加了一个小的变体,它也在C++ 11中工作,并且“内联”也在ARDUIONC++中工作(在重载上没有“内联”),上面的关于多个定义的消息——这是一个链接器消息,所以它确实编译了,我猜想ARDUINO变型不在内联“明显内联”函数作为其他编译器。p> 大概是这样的:

template <typename T>
struct SerialHelper {
    static T raw(T val) { return val; }
};

template <>
struct SerialHelper<std::string> {
    static const char* raw(const std::string& val) { return val.c_str(); }
};


class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, SerialHelper<Ts>::raw(args)...);
    }
};
模板
结构SerialHelper{
静态T原始(T val){return val;}
};
模板
结构SerialHelper{
static const char*raw(const std::string&val){return val.c_str();}
};
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(msg,SerialHelper::raw(args)…);
}
};

大概是这样的:

template <typename T>
struct SerialHelper {
    static T raw(T val) { return val; }
};

template <>
struct SerialHelper<std::string> {
    static const char* raw(const std::string& val) { return val.c_str(); }
};


class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, SerialHelper<Ts>::raw(args)...);
    }
};
模板
结构SerialHelper{
静态T原始(T val){return val;}
};
模板
结构SerialHelper{
static const char*raw(const std::string&val){return val.c_str();}
};
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(msg,SerialHelper::raw(args)…);
}
};

基于此,我将它用于这个非常简单的更改,它在C++11和Arduino C++中工作:

#include <string>

template <typename T> T raw(const T& x) {return x;}
inline const char* raw(const String& x) {return x.c_str();}

class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, raw(args)...);
    }
};

int main() {
    std::string greeting("hi");
    SerialOut::error("Message %d %s\n", 1, greeting);
}
#包括
模板T原始(常量T&x){return x;}
inline const char*raw(const String&x){return x.c_str();}
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(味精、原料(args)…);
}
};
int main(){
字符串问候语(“hi”);
SerialOut::错误(“消息%d%s\n”,1,问候语);
}
感谢@IgorTandetnik的评论,原因很清楚

基于此,我将其用于这个非常简单的更改,它在C++11和Arduino C++中工作:

#include <string>

template <typename T> T raw(const T& x) {return x;}
inline const char* raw(const String& x) {return x.c_str();}

class SerialOut {
public:
    template<class ...Ts>
    static void error(const char* msg, Ts... args) {
        printf(msg, raw(args)...);
    }
};

int main() {
    std::string greeting("hi");
    SerialOut::error("Message %d %s\n", 1, greeting);
}
#包括
模板T原始(常量T&x){return x;}
inline const char*raw(const String&x){return x.c_str();}
类串行输出{
公众:
模板
静态无效错误(常量字符*msg,Ts…args){
printf(味精、原料(args)…);
}
};
int main(){
字符串问候语(“hi”);
SerialOut::错误(“消息%d%s\n”,1,问候语);
}

感谢@IgorTandetnik的评论,原因很清楚

您已经专门化了
raw
,但是呼叫
raw(问候)
呼叫
raw
谢谢@IgorTandetnik,但这不是一个解释,请参阅下面我的答案,其中使用了重载。编译器可以选择重载,但不能选择专门化。请注意,我还尝试了单参数专门化。只需使用常规函数重载模板。专门化不参与重载解析;只有主模板可以。如果选择了它,那么编译器将检查它是应该实例化主模板还是使用其中一个专门化
raw(问候语)
与主模板匹配,
T
推断为
std::string
R
默认为与
T
相同(无法推断)。然后,这些模板参数与专门化不匹配,因此将实例化主模板。无过载分辨率