C++ Lambda expressions



Headlines
Functor

Rule(s)

Example Lambda_expression.Cpp.zip 

// '.h' file:
class Substituable_character { 
    private:
        std::unordered_set<char> _substitutes;
    public: // 'inline' is for pedagogical reasons *ONLY* (compactness of code)
        const char character;
        inline Substituable_character(char character, std::initializer_list<char> substitutes) : character(character) {
            for (auto substitute : substitutes) _substitutes.insert(substitute);
        }
        // Functor (later) used by 'std::transform' ('()' operator can take as many parameters as desired):
        inline const char operator() (char substitute) const { // '()' operator contextual definition
            if (_substitutes.find(substitute) == _substitutes.end()) return substitute;
            return character;
        }
        …
};

class Palindrome_word {
    private:
        std::string _word;
    public:
        inline Palindrome_word(const std::string& word) : _word(word) {} // 'inline' is for pedagogical reasons *ONLY* (compactness of code)
        void alter(const Substituable_character&);
        …
};

// '.cpp' file:
void Palindrome_word::alter(const Substituable_character& sc) {
    std::transform(_word.begin(), _word.end(), _word.begin(), sc); // 'sc' object is viewed as functor...
}
…
Substituable_character a('a', { 'à' }); // Aggregate initialization
Substituable_character e('e', { 'é', 'è', 'ê'}); // Aggregate initialization
Substituable_character u('u', { 'ù' }); // Aggregate initialization
// Etc.
std::cout << a.operator()('à') << std::endl; // 'a'
Palindrome_word pw("élu");
pw.alter(e);
std::cout << pw.toString() << std::endl; // 'elu'
Lambda expression

Rule(s)

Example (on the fly) Lambda_expression.Cpp.zip 

// '.h' file:
class Substituable_character {
    private:
        std::unordered_set<char> _substitutes;
    public:
        …
        std::string toString() const;
};

// '.cpp' file:
std::string Substituable_character::toString() const {
    std::string result(1, character);
    // Lambda expression ('&' allows the injection of local variables, e.g., 'result' by reference):
    std::for_each(_substitutes.begin(), _substitutes.end(), [&](char substitute) /* -> void */ {
        result += "-" + std::string(1, substitute);
    });
    return result;
}

Example (lambda expression is stored as a common object) Lambda_expression.Cpp.zip 

// '.h' file:
class Palindrome_word {
    private:
        std::string _word;
    public:
        inline Palindrome_word(const std::string& word) : _word(word) {} // 'inline' is for pedagogical reasons *ONLY* (compactness of code)
        void alter(const Substituable_character&);
        void alter_(const Substituable_character&);
        …
};
                    
void Palindrome_word::alter_(const Substituable_character& sc) {
    auto my_lambda = [sc](char substitute) /* -> const char */ {
        // By lazyness, one calls 'const char operator() (char substitute) const' in 'Substituable_character':
        return sc(substitute);
    };
    std::transform(_word.begin(), _word.end(), _word.begin(), my_lambda); // Lambda expression instead of explicit functor...
}

Capture

Example (capture by reference versus by value) Lambda_expression_.Cpp.zip 

std::string FB = "Franck Barbier";
char my_char = 'a', number_of_my_char = 0;
// Injecting one object by copy and the other one by reference:
std::for_each(FB.begin(), FB.end(), [my_char, &number_of_my_char] (const char& c) /* -> bool */ {
    if (c == my_char) {
        number_of_my_char++;
        return true;
    }
    return false;
});
assert(number_of_my_char == 2); // Two occurrences of 'a'

mutable keyword

Rule(s)

Example (reminder on mutable) Lambda_expression_.Cpp.zip 

'.h' file:
class Illustration_of_mutable {
    int _a;
    mutable int _b;
    void object_state_is_not_changed() const;
};

'.cpp' file:
void Illustration_of_mutable::object_state_is_not_changed() const {
    //    _a++; // Compilation error due to 'const'
    _b++; // 'mutable' inhibits 'const'
}

Example (mutable) Lambda_expression_.Cpp.zip 

std::string FB = "Franck Barbier";
char from = 'a', to = 'A';
bool changed = false;
auto my_mutable_functor = [from, to, changed](char& c) mutable {
    if (c == from) {
        c = to;
        changed = !changed; // Requires 'mutable'...
    }
    return changed;
};
std::for_each(FB.begin(), FB.end(), my_mutable_functor);
std::cout << FB << std::endl; // As expected: 'FrAnck BArbier'

from = 'b';
to = 'B'; // Pay attention: capture does not mean argument passing!
std::for_each(FB.begin(), FB.end(), my_mutable_functor);
std::cout << FB << std::endl; // No effect: 'FrAnck BArbier'

See also

Initializer

Rule(s)

Example (with initializer) Smart_pointers.Cpp.zip 

'.h' file:
class Mammoth : public Elephantidae {
private:
    const Mammoth* _best_friend = nullptr;
public: // 'inline' is for compactness only!
    const std::string name;
    inline Mammoth(const std::string& name = "") : name(name) {};
    inline void best_friend(const Mammoth* best_friend) {_best_friend = best_friend;};
};

'.cpp' file:
std::unique_ptr<Mammoth> the_very_first_Mammoth(new Mammoth("the_very_first_Mammoth"));
// Compilation error (capture by copy cannot apply on movable-only objects as 'std::unique_ptr' objects):
//    auto my_lambda_expression = [the_very_first_Mammoth] (std::unique_ptr<Mammoth>&& m) {
//        m->best_friend(the_very_first_Mammoth.get());
//    };
// Capture with initializer (C++14):
auto my_lambda_expression = [best_friend = the_very_first_Mammoth.get()] (std::unique_ptr<Mammoth>&& m) {
    m->best_friend(best_friend);
};
my_lambda_expression(std::move(m0)); // 'the_very_first_Mammoth' becomes best friend of 'm0'...
assert(m0.get() != nullptr); // Forced 'move' does not empty 'm0'...

Lambda expression and STL

Rule(s)

Example (get the values of a map) Smart_pointers.Cpp.zip 

std::unordered_map<std::string, std::unique_ptr<Mammoth>&& > zoo; // Indexed access based on names...
std::unique_ptr<Mammoth> m = std::make_unique<Mammoth>("m");
zoo.emplace(m.get()->name, std::move(m)); // 'zoo.try_emplace(m.get()->name, std::move(m));' // C++17
assert(m.get() != nullptr); // Forced 'move' does not empty 'm'...

std::vector<std::unique_ptr<Mammoth> > values; // Arrrrrgggggllll: no 'values' function on maps!
std::transform(std::begin(zoo), std::end(zoo), std::back_inserter(values),
    [](/* auto */ /* -> from C++14 */ const decltype(zoo)::value_type& pair) {
        static_assert(std::is_same_v<decltype(pair.second), std::unique_ptr<Mammoth>&&>, "Type checking failed, why?");
        return std::move(pair.second); // This empties the original map of resources!
    });
assert(m.get() == nullptr); // Forced 'move' emptied 'm'...

Example (get the values of a map cont'd) Smart_pointers.Cpp.zip 

std::vector<const Mammoth*> values;
std::transform(std::begin(zoo), std::end(zoo), std::back_inserter(values),
    [](const auto& pair) {
        return pair.second.get();
    });
assert(m.get() != nullptr);

Example (constructibility and copyability) Lambda_expression_.Cpp.zip 

'.h' file:
class Election {
    std::string _separator;
    std::unordered_multimap<std::string, std::string> _situation;
public:
    static const std::string Candidate;
    static const std::string Challenger;
    static const std::string President;
    static const std::string Prime_minister;
    Election(const std::string&, const std::initializer_list<std::unordered_multimap<std::string, std::string>::value_type>&);
    std::string candidates() const;
    void change_separator(const std::string&);
};

'.cpp' file:
std::string Election::candidates() const {
    std::string result;

    auto is_candidate_predicate = [](const std::unordered_multimap<std::string, std::string >::value_type& someone_in_role) -> bool {
        // As shown, static members, e.g., 'Election::Candidate', do not require capture:
        return Election::Candidate.compare(someone_in_role.second) == 0;
    };

    decltype(is_candidate_predicate) is_candidate_predicate_; // Ok Visual Studio C++20 while "niet" for C++14-17
    std::cout << "'std::is_trivially_constructible_v<decltype(is_candidate_predicate)>': " <<
    std::is_trivially_constructible_v<decltype(is_candidate_predicate)> << std::endl;

    decltype(is_candidate_predicate) copy_of_is_candidate_predicate = is_candidate_predicate;
    std::cout << "'std::is_trivially_copyable_v<decltype(is_candidate_predicate)>': " <<
    std::is_trivially_copyable_v<decltype(is_candidate_predicate)> << std::endl; 'true'

    copy_of_is_candidate_predicate = is_candidate_predicate; // Ok Visual Studio C++20 while "niet" for C++14-17
    std::cout << "'std::is_trivially_assignable_v<decltype(is_candidate_predicate), decltype(is_candidate_predicate)>': " <<
    std::is_trivially_assignable_v<decltype(is_candidate_predicate), decltype(is_candidate_predicate)> << std::endl;
    …
}

Example (predicate, unary and binary operations) Lambda_expression_.Cpp.zip 

std::string Election::candidates() const {
    …
    if (std::none_of(std::begin(_situation), std::end(_situation), is_candidate_predicate))
        result += "Nobody is " + Election::Candidate;
    else {
        std::unordered_multimap< std::string, std::string > candidates;
        std::copy_if(/*std::execution::par,*/ _situation.begin(), _situation.end(), std::inserter(candidates, candidates.end()),
            [](auto& someone_in_role) {
                return Election::Candidate.compare(someone_in_role.second) == 0;
            });

        std::vector<std::string > keys;
        std::transform(std::begin(candidates), std::end(candidates), std::back_inserter(keys),
            [](decltype(candidates)::value_type& pair) {
                return pair.first;
        });

	// result = Election::Candidate + ": " + std::reduce(/*std::execution::par,*/ keys.begin(), keys.end(), result,
	//  [this](const std::string& k1, const std::string& k2) {
	//      // Compilation error comes from 'candidates' is a 'const' member function...:
	//      this->change_separator(" # "); // 'change_separator' is a non-'const' member function...
	//          return k1 + this->_separator + k2; // Note that '[=]' is enough to capture 'this->_separator'
	//      });

        result = Election::Candidate + ": " + std::reduce(/*std::execution::par,*/ keys.begin(), keys.end(), result,
            // Initializer (C++14) and '*this' (C++17):
            [my_this = *this](const std::string& k1, const std::string& k2) mutable {
                my_this.change_separator(" # "); // 'change_separator' is a non-'const' member function...
                    return k1 + my_this._separator + k2;
            });
        }
    return result;
}

Generic lambda expression (C++14)

Rule(s)

Example (principle) Lambda_expression_.Cpp.zip 

class { // Closure type
public:
    template<typename X> void operator()(X& x) const {x++;}
} increment;
…
int x = 0;
// More simply, '::increment(x);'
::increment.operator()/*<int>*/(x); // Instantiation of template member function in closure type
std::cout << "::increment(x): " << x << std::endl; // '1'
auto increment = [](auto& x) { // Generic lambda...
    x++;
};
increment(x); // Instantiation of generic lambda...
std::cout << "increment(x): " << x << std::endl; // '2'

Example (instantiation from iterator type) Lambda_expression_.Cpp.zip 

std::unordered_multimap<std::string, std::string > fr_2022_presidential_election {
    {"Macron", "President"}, {"Macron", "Candidate"},
    {"Castex", "Prime minister"},
    {"Mélanchon", "Challenger"}, {"Mélanchon", "Candidate"},
    {"Le Pen", "Challenger"}, {"Le Pen", "Candidate"}
};
std::unordered_multimap<std::string, std::string >::iterator iterator = fr_2022_presidential_election.begin();
increment(iterator); // Instantiation of generic lambda...
std::cout << "increment(iterator): " << (*iterator).second << std::endl; // No order, so... value of any element

From generic to polymorphic lambda expression

Rule(s)

Example (principle) Inheritance_genericity_gcc.Cpp.zip 

'.h' file:
class Cabbage : public Vegetable {
public:
    bool operator==(const Cabbage&) const;
    bool operator!=(const Cabbage&) const;
};

class Carrot : public Vegetable {
public:
    bool operator==(const Carrot&) const;
    bool operator!=(const Carrot&) const;
    Vegetable_colors color() const;
};

'.cpp' file:
bool Cabbage::operator==(const Cabbage& cabbage) const {
    return std::is_same<decltype(*this), decltype(cabbage)>::value; // For simplification
}
bool Cabbage::operator!=(const Cabbage& cabbage) const {
    return !this->operator==(cabbage);
}
bool Carrot::operator==(const Carrot& carot) const {
    return std::is_same<decltype(*this), decltype(carot)>::value; // For simplification
}
bool Carrot::operator!=(const Carrot& carot) const {
    return !this->operator==(carot);
}
bool operator==(const Carrot& carot, const Cabbage& cabbage) {
    return true; // "Mélanger des choux et des carottes !"
}
bool operator==(const Cabbage& cabbage, const Carrot& carot) {
    return false; // "Mélanger des carottes et des choux !"
}
…
// Generic and polymorphic lambda (C++14) -> https://stackoverflow.com/questions/26435052/using-template-parameter-in-a-generic-lambda
// Visual Studio 2020 is not able to deal with 'decltype(a)':
auto equals_generic = [](auto a, decltype(a) b) {
    return a == b;
};
auto equals_polymorphic = [](auto a, auto b) {
    return a == b;
};

std::cout << std::boolalpha << equals_generic(Cabbage{}, Cabbage{}) << std::endl;
std::cout << std::boolalpha << equals_generic(Carrot{}, Carrot{}) << std::endl;
// std::cout << std::boolalpha << equals_generic(Cabbage{}, Carrot{}) << std::endl; // Compilation error due to non-polymorphic:
// std::cout << std::boolalpha << equals_generic(Carrot{}, Cabbage{}) << std::endl; // Compilation error due to non-polymorphic:

std::cout << std::boolalpha << equals_polymorphic(Carrot{}, Carrot{}) << std::endl;
std::cout << std::boolalpha << equals_polymorphic(Cabbage{}, Cabbage{}) << std::endl;
std::cout << std::boolalpha << equals_polymorphic(Cabbage{}, Carrot{}) << std::endl;
std::cout << std::boolalpha << equals_polymorphic(Carrot{}, Cabbage{}) << std::endl;

See also