Examples of practical usage of Boost::MPL?
Asked Answered
F

8

30

Can you share any real-world examples of Boost::MPL usage (except lambdas), just to let me better understand its purposes and field of practical usage? The MPL documentation tutorial has a dimensional analysis example, but maybe because it's such an academic example it hasn't given me a feeling of Boost::MPL and when it can be effectively used.

Fatuous answered 9/1, 2011 at 11:42 Comment(0)
L
15

I've used Boost.Mpl to generate variant-like classes.

For example, given a MPL type list such as this:

typedef boost::mpl::set<Foo, Bar, Baz> type_set;

I then use boost::mpl::fold to build a chain of classes derived from each others which each adds an std::unordered_set of one of the types in the type set. The end result is a class which contains an unordered_set<Foo>, an unordered_set<Bar> and an unordered_set<Baz>.

And because the class is specified in terms of a boost::mpl::set, I can iterate over these types to automatically generate other functions as well, such as an operator== which compares all of the unordered_sets.

Lajoie answered 9/1, 2011 at 14:15 Comment(0)
A
15

The fact is, Boost.MPL, like Boost.Preprocessor, are really building blocks.

Most of the times, you probably use it through other libraries, as a number of Boost libraries are built upon those two.

For example:

  • Boost.Fusion (which crosses the gaps between compile-time and run-time realms)
  • Boost.MultiIndex (for an easier interface)
  • Boost.Unit (for dimensional analysis)
  • Boost.Variant may, I think, also depends on it

You may use it unknowningly already :)

Aer answered 9/1, 2011 at 15:57 Comment(0)
F
6

I use a more enhanced dimensional analysis library called Boost.Units.

I've developed a compile-time reflection library and then used that library to build a generic class that provides runtime-reflection to any compile-time reflected type passed in. I've used that support to automatically generate UI components to edit the properties of such reflected types.

It's also paramount to the distribution of events within our application. For instance, when someone changes the units they wish the system to be in, I don't have to teach that system that new items have been added to given devices because the code uses MPL to analyze those types and just knows that something's been added and changes it.

I've just used metaprogramming techniques to wrap up the Qt signals into something that regains the type safety removed by their system and is able to connect with any functional entity.

But to tell the truth, you've almost certainly used practically applied metaprogramming techniques already when you've used standard algorithms like sort. A decent implementation of the sort algorithm uses a less evolved form of metaprogramming to analyze the iterators passed in and then uses tag-dispatching to initiate a sort algorithm capable of fully utilizing the features of those iterators.

Quite frankly, if you're not doing metaprogramming then you're not utilizing the power of C++ and you may as well be using something else.

Furculum answered 9/1, 2011 at 11:52 Comment(3)
I think the question is about Boost.MPL and not metaprogramming in general.Lajoie
You can't talk about MPL without all the stuff that lead up to it.Furculum
but you can ask "do you use Boost.MPL" without asking "do you use any other example of template metaprogramming", in the same way that you can ask "do you drive a Volvo" without asking "Do you drive a car"Lajoie
A
6

When it comes to build matching engine, mostly for Exchange or DarkPool in trading area, we usually need to check if 2 orders could match or not (or we say could cross or not), there could be many aspects to check which we call as Rules, and here are the key requirements in term of organizing these rules:

  • It should be easy to add a new rule and get applied
  • It should be convenient to organize rules into different groups to apply the checks
  • Because the rule check are invoked quite frequently - well, of couse, that is the sole job of a matching engine, we want it as optimal as possible

This is a great fit to use boost mpl, which could use compile time sequence boost::mpl::vector to organize rules, and get them applied with boost::mpl::for_each.

The idea is best illustrated with an example:

  • It is straightforwrad to add a new rule by simply defining a new Rule class
  • It is convenient to group rules using boost::mpl::vector, and use it as a template parameter for canCross check
  • Because most of the setting up work are done in compile time, it is fast.
#include <iostream>
#include <vector>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/for_each.hpp>

using namespace std;

struct Order {};

struct Rule1
{
    const char* name() const { return "Rule1"; }
    bool apply(const Order& a, const Order& b) const {  cout << "Checking Rule1..." << endl; return true; }
};

struct Rule2
{
    const char* name() const { return "Rule2"; }
    bool apply(const Order& a, const Order& b) const {  cout << "Checking Rule2..." << endl; return false;}
};

struct Rule3
{
    const char* name() const { return "Rule3"; }
    bool apply(const Order& a, const Order& b) const {  cout << "Checking Rule3..." << endl; return false;}
};

struct Rule4
{
    const char* name() const { return "Rule4"; }
    bool apply(const Order& a, const Order& b) const {  cout << "Checking Rule4..." << endl; return true;}
};

struct RuleApplicator
{
    RuleApplicator(bool& success, std::vector<const char*>& failedRules, const Order& order1, const Order& order2):
        _success(success),
        _failedRules(failedRules),
        _order1(order1),
        _order2(order2)
    {}
    template <typename U> void operator() (U rule)
    {
        if(!rule.apply(_order1, _order2))
        {
            _success = false;
            _failedRules.push_back(rule.name());
        }
    }

private:
    bool& _success;
    std::vector<const char*>& _failedRules;
    const Order& _order1;
    const Order& _order2;
};

template <class Rules>
bool canCross(const Order& a, const Order& b)
{
    bool success = true;
    std::vector<const char*> failedRules;
    RuleApplicator applicator(success, failedRules, a, b);
    boost::mpl::for_each<Rules>(applicator);
    if (!success)
    {
        cout << "Can't cross due to rule check failure:";
        for(const char* ruleName: failedRules)
        {
            cout << ruleName << " ";
        }
        cout << endl;
        return false;
    }
    else
    {
        cout << "Can cross!" << endl;
        return true;
    }
}

int main(int argc, char** argv)
{
    Order a, b;
    canCross<boost::mpl::vector<Rule1, Rule4>>(a, b);
    cout << endl;
    canCross<boost::mpl::vector<Rule1, Rule2, Rule3, Rule4>>(a, b);
}


You will see output as:

Checking Rule1...
Checking Rule4...
Can cross!

Checking Rule1...
Checking Rule2...
Checking Rule3...
Checking Rule4...
Can't cross due to rule check failure:Rule2 Rule3
Anything answered 24/3, 2019 at 2:2 Comment(0)
A
5

If your application has heavy logic in dealing with key-value pairs, you will need a ultra efficent way to get value from key, typical hashmap works well, but if possible keys are known upfront, optimzation could be done use boost::mpl, with an array, and a method to convert your key to array index at compile time, this is certainly more efficent.

Here is an example on handling fix message, which is a message contains various key-value pairs, it is used heavily in financial trading applications:

#include <iostream>
#include <array>
#include <string>
#include <unordered_set>
#include <boost/mpl/vector_c.hpp>
#include <boost/mpl/find.hpp>
#include <boost/mpl/for_each.hpp>
#include <boost/mpl/integral_c.hpp>
#include <boost/mpl/size.hpp>

using namespace std;
using namespace boost;


struct TagEntity
{
    bool isValid;
    std::string value;
};

template<class CommonTags>
struct FixMsg
{
    static constexpr uint32_t CommonTagsCount = mpl::size<CommonTags>::type::value;

    template<int Tag>
    constexpr uint32_t index()
    {
        constexpr auto idx = mpl::find<CommonTags, mpl::integral_c<int, Tag>>::type::pos::value;  // this is the key step: convert tag to index in compile time
        static_assert(idx < CommonTagsCount, "tag not found");
        return idx;
    }
    template<int Tag>
    TagEntity& getTagEntity()
    {
        return _commonTags[index<Tag>()];
    }

    std::array<TagEntity, CommonTagsCount> _commonTags; // or else use std::unordered_set, which is not as fast as this approach: absolute O(1) in runtime
};


int main(int argc, char** argv)
{
    using MyCommonTags = mpl::vector_c<int,
          11,
          35,
          10914,
          10916>;

    FixMsg<MyCommonTags> fixMsg;
    auto& tagEntity = fixMsg.getTagEntity<11>();
    tagEntity.isValid = true;
    tagEntity.value = "Get tag entity in O(1)";

    cout << tagEntity.value << endl;

Anything answered 24/3, 2019 at 4:2 Comment(0)
V
4

I use boost::mpl (and boost::fusion) extensively in my stat_log library. This library allows the user to specify a hierarchy of statistic and logging tags and their associated behaviors, i.e. per-tag statistic types (histogram, counter, etc).

I rely heavily on metaprogramming to do the right thing with the user does:

stat_log::writeStat<IP_PKTS_RCVD>(450);

For example if the user defines the type trait:

template <>
struct stat_tag_to_type<IP_PKTS_RCVD>
{
   using type = Accumulator<
        stat_log::HistogramCount<
            int,
            1, //start bin
            1500, //stop bin
            10 //num_bits
        >
     >;
};

the "writeStat" call above will proxy (at compile time) to a histogram statistic. The powerful aspect of this design technique is the "writeStat" call site is not at all coupled with the particular statistic chosen.

I also use a wealth of MPL and boost::fusion to actually view the stats. Per your question, see the following files for the highest concentration of boost::mpl:

https://github.com/rjmccabe3701/stat_log/blob/master/include/stat_log/util/stat_log_impl.h https://github.com/rjmccabe3701/stat_log/blob/master/include/stat_log/util/tag_commander.h https://github.com/rjmccabe3701/stat_log/blob/master/include/stat_log/stat_log.h

especially the nifty template meta "function" in stat_log_impl.h:

//This template is used in conjunction with an MPL algorithm
// with the same semantics as mpl::find_if.
//BoolFunc is the "condition" metafunction.
//StatTagFunc is a metafunction that transforms the given
//   stat_tag into something the algorithm requires.
//   For example the "Identity" metafunction would work here.
//StatTagArgs is extra arguments to the BoolFunc
template <template<typename...> class BoolFunc,
          template<typename...> class StatTagFunc,
          class... StatTagArgs>
struct tag_node_query
{
   template<typename TheTagNode>
   struct apply
   {
      using stat_tag = typename TheTagNode::tag;
      using type = std::integral_constant
         <
            bool,
            BoolFunc<
               typename StatTagFunc<stat_tag>::type,
               StatTagArgs...
            >::value
         >;
   };
};
Vapid answered 19/9, 2015 at 13:46 Comment(1)
Broken links and deception :(Paternalism
U
3

To add to Matthieu's answer, it's also used quite extensively throughout both Boost.Python and Luabind.

Uncommercial answered 12/3, 2011 at 19:47 Comment(1)
boost.proto, boost.spirit (qi,karma,lex)... actually in most other boost libraries...Crinite
S
2

Something funny I did: https://github.com/edubois/static-factorial/blob/master/main.cpp

It uses a tiny part of boost::mpl to statically compute the value of factorial<8>()...

This can help to understand the main idea.

Sleepy answered 19/1, 2015 at 21:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.