匹配自定义函子或stod,stoi等的模板

时间:2018-07-03 15:44:40

标签: c++ c++11 templates variadic-templates

我试图在名为string的类中将键值参数存储为ModelConfig。然后,我想使用自定义转换功能或标准功能stodstofstoi等自动将这些值转换为特定类型。

如果提供自定义转换功能,我的班级就可以成功解析参数,但是我无法确定如何也接受标准功能。这是我的方法:

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但可以具有其他默认值的函数?

4 个答案:

答案 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的情况下执行此操作,并且在需要时不使用预处理程序指令即可

  • 通过显式转换标准函数指针。 stringwstring的标准函数已重载,因此编译器需要我们的帮助来确定要应用哪个函数

  • ,并通过稍微更改函子的签名以使其适应这些标准函数的签名,因为它们具有第二个参数。

这些更改实际上将是微小的:

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

这有一些明显的局限性,但在某些情况下可能有用。