我试图在名为string
的类中将键值参数存储为ModelConfig
。然后,我想使用自定义转换功能或标准功能stod
,stof
,stoi
等自动将这些值转换为特定类型。
如果提供自定义转换功能,我的班级就可以成功解析参数,但是我无法确定如何也接受标准功能。这是我的方法:
class ModelConfig
{
public:
ModelConfig(void) = default;
void addParam(std::string pname, std::string pvalue) {
m_params[pname] = pvalue;
}
template <class F, typename... Args, class T = typename std::result_of<F&&(const std::string&, Args...)>::type>
T getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname));
}
private:
std::map<std::string, std::string> m_params;
};
上面的类可以用以下方法进行测试:
#include <iostream>
#include <map>
#include <functional>
#include "ModelConfig.hpp"
int main(void)
{
ModelConfig mc;
mc.addParam("p1_float", "123.4");
mc.addParam("p2_double", "56.7");
mc.addParam("p3_bool", "true");
mc.addParam("p4_int", "-321");
auto functord = [](const std::string& s) {
return std::stod(s);
};
std::cout << mc.getParam("p2_double", functord) << "\n"; // OK.
std::cout << mc.getParam("p2_double", std::stod) << "\n"; // Error.
return 0;
}
如何修改getParam
以接受其第一个参数为string
但可以具有其他默认值的函数?
答案 0 :(得分:3)
std::stod
已重载,因此编译器无法推断要使用哪个函数。
您可以使用宏来编写通用包装器:
#define wrapper(f) \
( [] (auto&&... args) -> decltype(auto) { \
return f(std::forward<decltype(args)>(args)...); \
} )
然后通过以下方式调用它:
std::cout << mc.getParam("p2_double", wrapper(std::stod)) << "\n";
答案 1 :(得分:3)
另一种更好的IMO设计是将值存储为std/boost::variant<bool, long, double, std::string>
,并在I / O期间将其转换为字符串。这也可以在加载时及早检测到配置文件错误,而不是在首次访问值时检测到错误,后者可能会在以后发生,并且会使您的应用程序在用户面前崩溃。
要求此API的用户始终传递转换函数很麻烦。您可以使用boost::lexical_cast
将字符串转换为T
:
#include <string>
#include <iostream>
#include <unordered_map>
#include <boost/lexical_cast.hpp>
struct ConvertProxy {
std::string const* value_;
template<class T>
T as() const {
return boost::lexical_cast<T>(*value_);
}
template<class T>
operator T() const {
return this->as<T>();
}
};
class ModelConfig {
std::unordered_map<std::string, std::string> m_params;
public:
void addParam(std::string pname, std::string pvalue) {
m_params[pname] = pvalue;
}
ConvertProxy getParam(std::string pname) const {
return {&m_params.at(pname)};
}
};
int main() {
ModelConfig mc;
mc.addParam("p1_float", "123.4");
mc.addParam("p2_double", "56.7");
mc.addParam("p3_bool", "true");
mc.addParam("p4_int", "-321");
// Example syntax.
double d1 = mc.getParam("p2_double");
auto d2 = mc.getParam("p2_double").as<double>();
auto d3 = static_cast<double>(mc.getParam("p2_double"));
std::cout << mc.getParam("p2_double").as<double>() << "\n";
std::cout << static_cast<double>(mc.getParam("p2_double")) << "\n";
}
boost::lexical_cast
的界面在此处提供了一种简单的解决方案。如果您不能使用boost::lexical_cast
,则应该使用类似的界面编写自己的代码。
答案 2 :(得分:1)
您可以在没有第三方lib的情况下执行此操作,并且在需要时不使用预处理程序指令即可
通过显式转换标准函数指针。 string
和wstring
的标准函数已重载,因此编译器需要我们的帮助来确定要应用哪个函数
,并通过稍微更改函子的签名以使其适应这些标准函数的签名,因为它们具有第二个参数。
这些更改实际上将是微小的:
在ModelConfig
中:
class ModelConfig
{
[...]
// Adapted the functor's signature to comply to standard functions' signatures:
template <class F, typename... Args, class T = typename std::result_of<F && (const std::string&, size_t *)>::type>
T getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname), 0);
}
[...]
};
在main()
中:
int main(void)
{
[...]
// Adapted the functor to standard functions' signature
auto functord = [](const std::string& s, size_t * pos) {
return std::stod(s, pos);
};
// Unchanged, no need
std::cout << mc.getParam("p2_double", functord) << "\n"; // Still OK.
// Cast to determine which overload to use. The typedef helps having things readable.
typedef double(*StandardFunctionSignature)(const std::string&, size_t*);
std::cout << mc.getParam("p2_double", static_cast<StandardFunctionSignature>(std::stod)) << "\n"; // NO Error, it works now.
[...]
}
答案 3 :(得分:1)
如果您知道传入的重载集合的签名,则可以进行额外的重载,以从该集合中捕获特定的函数指针。
template <class F>
auto getParam(std::string pname, F&& pconv_functor) const
{
return pconv_functor(m_params.at(pname));
}
template <class F>
auto getParam(std::string pname, F(*pconv_functor)(const std::string&, std::size_t*)) const
{
return pconv_functor(m_params.at(pname), 0);
}
这有一些明显的局限性,但在某些情况下可能有用。