【问题标题】:Use mpl::vector as function table使用 mpl::vector 作为函数表
【发布时间】:2025-12-28 08:25:12
【问题描述】:

我正在编写一些 C++,它读取 XML 字符串并使用与 XML 文件中的属性键匹配的值填充一些结构成员。目前,第一遍生成键值对的 stl::unordered_map。下一步是解释值字符串并将它们作为要存储在结构中的目标类型返回。有些类型有点复杂,但我有可以转换它们的方法。

我想要做的是使用 mpl::vector 将键映射到结构成员,使用 get_value_* 方法转换值。我认为它看起来有点像:

typedef boost::mpl mpl;
using namespace std;

template<string K, typename T, T& dest, boost::function<void(MapTypePtr, string, T& dest)> >
struct MapperRow {};

struct Mapper : mpl::vector<
    MapperRow < "death_star_radius", Length, death_star.radius, get_value_units >,
    MapperRow < "death_star_energy", Energy, death_star.energy, get_value_units >,
    MapperRow < "operational",       bool,   death_star.operational, get_value_simple >,
    MapperRow < "num_tie_fighters",  int,    death_star.tie_fighers, get_value_simple >
> {};

Length 和 Energy 类型是 boost::units::quantities 的 typedef。

这对 boost 元编程可行吗?如果我在正确的轨道上,我该如何让它运行?我需要迭代 mpl::vector 吗?

【问题讨论】:

  • 我还不明白这个问题,但是你不能像你那样在模板声明中使用像字符串这样的类。这仅适用于“bool”或“unsigned”等基本类型。
  • 可能有一个 mpl 包装器类型以某种方式完成它?我可能会问不可能的问题,但我已经看到与 boost 状态机类似的东西,所以我希望我能接近。
  • 是的,有办法。不太难(使用一个有很多字符作为参数的模板)。为此,我发现使用代码生成器通常更容易,这在您的情况下可能很简单,因为您已经拥有 XML,并且只需要 xsl 或其他类型的转换器来生成代码。
  • struct death_star_radius { const char* value() { return "death_star_radius"; } }; type stuff 可用于将字符串表示为类型。在 C++11 中,struct { const char* value() { return "death_star_radius"; } } 作为匿名类型也可以工作。遗憾的是,据我所知,字符串文字不能用作直接模板参数。
  • 这篇文章cpp-next.com/archive/2012/10/… 提供了一些关于在模板元程序中使用字符串的见解。

标签: c++ metaprogramming boost-mpl


【解决方案1】:

这绝对是可能的(见下文),但我不确定这是否值得麻烦...您不能使用更简单的方法,也许使用继承和多态性?

不过,这里有一个使用 Boost.MPL 的可行解决方案:

// MapperRow holds the necessary parsing informations: type of the member,
// type of the object, pointer to the appropriate member, parsing function
template<typename Type, typename Clazz, Type Clazz::*Member, 
         void (*Parser)(const std::string &, Type &)>
struct MapperRow
{
    typedef Type type;
    typedef Clazz clazz;
    typedef Type Clazz::*memberType;
    static const memberType member;
    typedef void (*parserType)(const std::string &, Type &);
    static const parserType parser;
};

template <typename Type, typename Clazz, Type Clazz::*Member,
          void (*Parser)(const std::string &, Type &)>
const typename MapperRow<Type, Clazz, Member, Parser>::memberType
    MapperRow<Type, Clazz, Member, Parser>::member = Member;

template <typename Type, typename Clazz, Type Clazz::*Member,
          void (*Parser)(const std::string &, Type &)>
const typename MapperRow<Type, Clazz, Member, Parser>::parserType
    MapperRow<Type, Clazz, Member, Parser>::parser = Parser;


// fill iterates over a map key->MapperRow, trying to find the given key.
// if found, it calls the parsing function, otherwise it asserts false (should
// probably throw an exception instead)
template <typename Clazz, typename First, typename Last>
struct fill_impl
{
    static void apply(Clazz &obj, const std::string &key,
                      const std::string &value)
    {
        typedef typename mpl::deref<First>::type entry;
        static const char *curKey = 
            mpl::c_str< typename 
                mpl::first<entry>::type
            >::value;

        if (key == curKey)
        {
            typedef typename mpl::second<entry>::type Row;

            Row::parser(value, obj.*Row::member);
        }
        else
        {
            fill_impl<
                Clazz, typename
                mpl::next<First>::type,
                Last
            >::apply(obj, key, value);
        }
    }
};

template <typename Clazz, typename Last>
struct fill_impl<Clazz, Last, Last>
{
    static void apply(Clazz &obj, const std::string &key,
                      const std::string &value)
    {
        assert(false && "key not found");
    }
};

template <typename Map, typename Clazz>
void fill(Clazz &obj, const std::string &key, const std::string &value)
{
    fill_impl< 
        Clazz, typename 
        mpl::begin<Map>::type, typename 
        mpl::end<Map>::type
    >::apply(obj, key, value);
}

使用示例:

template <typename T>
void get_value_units(const std::string &str, T &value)
{
    value = T::from_value(boost::lexical_cast<typename T::value_type>(str));
}

template <typename T>
void get_value_simple(const std::string &str, T &value)
{
    value = boost::lexical_cast<T>(str);
}

typedef boost::units::quantity<boost::units::si::energy> Energy;
typedef boost::units::quantity<boost::units::si::length> Length;

struct DeathStar
{
    Length radius;
    Energy energy;
    bool operational;
    int tie_fighters;
};

// Could be clearer with MPLLIBS_STRING*
typedef mpl::map<
    mpl::pair<
        mpl::string<'deat','h_st','ar_r','adiu','s'>, 
        MapperRow< Length , DeathStar, &DeathStar::radius, 
                   &get_value_units<Length> > >,
    mpl::pair<
        mpl::string<'deat','h_st','ar_e','nerg','y'>,
        MapperRow< Energy, DeathStar, &DeathStar::energy, 
                   &get_value_units<Energy> > >,
    mpl::pair< 
        mpl::string<'oper','atio','nal'>, 
        MapperRow< bool, DeathStar, &DeathStar::operational, 
                   &get_value_simple<bool> > >,
    mpl::pair< 
        mpl::string<'num_','tie_','figh','ters'>, 
        MapperRow< int, DeathStar, &DeathStar::tie_fighters, 
                   &get_value_simple<int> > >
> death_star_map;

int main()
{
    DeathStar ds;
    fill<death_star_map>(ds, "death_star_radius", "12");
    fill<death_star_map>(ds, "death_star_energy", "34");
    fill<death_star_map>(ds, "operational", "1");
    fill<death_star_map>(ds, "num_tie_fighters", "56");

    std::cout << "Radius: " << ds.radius << '\n';
    std::cout << "Energy: " << ds.energy << '\n';
    std::cout << "Operational: " << std::boolalpha << ds.operational << '\n';
    std::cout << "Tie fighters: " << ds.tie_fighters << '\n';
}

* MPLLIBS_STRING

【讨论】:

  • 感谢您的回答。不,这不值得麻烦,但对我来说,这似乎应该是可能的,并且可以帮助我理解 mpl。我会调解你的答案,看看我是否获得启蒙!