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...
}
Rule(s)
-
mutable
means that the redefinition
of the ()
operator is no longer const
.
Deeply speaking, a capture-by-copy object is a member attribute in an instance of the closure type.
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
Rule(s)
- STL containers and generic algorithms are fond of lambda expressions that are applied on individual elements.
These are predicates for tests, unary operations for transformations or binary operations for reductions, etc.
this
may be captured in an implicit way using [=]
or in an explicit way using [this]
.
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;
}
Rule(s)
- Generic lambda expressions come with C++14
and the type deduction policy associated with
auto
that changes between C++11 and C++14.
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
Rule(s)
- Generic lambda expressions come with C++14
and the type deduction policy associated with
auto
that changes between C++11 and C++14.
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;