1
votes

Let's see a real life example:

class RuleNameConverter {
public:
    RuleNameConverter(const boost::property_tree::ptree& pt);
    int toIdentifier(const std::string& name) const;
    std::string toName(const int id) const;

private:
    using Bimap = boost::bimap<std::string, int>;
    Bimap bimap_;
};

Where the constructor is this:

RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) {
    for (const auto& item : pt) {
        if (item.first == "rule") {
            auto name = item.second.get < std::string > ("<xmlattr>.name");
            auto id = item.second.get<int>("<xmlattr>.id");
            bimap_.insert(Bimap::value_type { name, id });
        }
    }
}

Assume you want a const member attribute:

...
    const Bimap bimap_;
};

You must initialize it in the initializer list, not in the constructor body. It's initialization is non trivial, so you must delegate a function to compute its value. You can use the value returned by a lambda, taking advantages of the move semantics (no copy of temporary objects):

RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) :
        bimap_ { [&pt]() {
            Bimap results;
            for (const auto& item : pt) {
                if (item.first == "rule") {
                    auto name = item.second.get < std::string > ("<xmlattr>.name");
                    auto id = item.second.get<int>("<xmlattr>.id");
                    results.insert(Bimap::value_type {name, id});
                }
            }
            return results;
        }() } {
}

Are there any drawbacks to using this technique? Is it worth the trouble? I find it slightly less readable, but what about performance?

2
Hmm. I would double check the capture scope rules just to make sure there isn't a quirk there (you are in a strange context) -- probably not, after 2 standard revisions, especially in practice. - Yakk - Adam Nevraumont
Change return results; to return std::move(results);. Yes, compilers have RVO, but this way is move verbose. - GreenScape
@GreenScape I remember C++11 standard require RVO in this case, right? - Alessandro Pezzato
C++11 doesn't require RVO, but if RVO isn't performed, it does require attempting to use a move constructor. There is absolutely no reason for writing return std::move(results);. - user743382
@hvd, how exactly move operator is invoked in return results; ? - GreenScape

2 Answers

2
votes

Performance-wise, it should not matter all that much. You don't copy around any Bitmap objects, and the construction of your lambda should not take any noticeable time.

But for readability, I would create a static member function instead of a lambda here:

class RuleNameConverter {
public:
    RuleNameConverter(const boost::property_tree::ptree& pt);
private:
    static Bitmap createBitmap(const boost::property_tree::ptree& pt);
};

RuleNameConverter::RuleNameConverter(const boost::property_tree::ptree& pt) :
    bimap_ { createBitmap(pt) } {
}

Bitmap RuleNameConverter::createBitmap(const boost::property_tree::ptree& pt) {
    Bimap results;
    for (const auto& item : pt) {
        if (item.first == "rule") {
            auto name = item.second.get < std::string > ("<xmlattr>.name");
            auto id = item.second.get<int>("<xmlattr>.id");
            results.insert(Bimap::value_type {name, id});
        }
    }
    return results;
}

When you need to initialise several members using helper functions, creating a new lambda for each member leads to an unmaintainable mess in the constructor initialiser list, but several helper functions don't need to have that problem. Additionally, if you add constructor overloads, createBitmap can be easily called from multiple constructors.

Alternatively, use a regular non-member function if the body of createBitmap is not really specific to your RuleNameConverter.

1
votes

You could wrap the Bimap in another class, where its constructor would have the exact same body as the lambda.

I can't see how using a lambda to avoid a superficial class in this case would lead to any problems, except its intent is perhaps less clear, because it doesn't have a name (but that's the case with pretty much any lambda).