Rule(s)
- As many other languages (Java, JavaScript, Python…), C++ allows the possibility of having a variable number of arguments for a function (a.k.a. variadic function).
Example Variadic.Cpp.zip
// 'Variable_number_of_arguments.h' file #ifndef _Variable_number_of_arguments_H #define _Variable_number_of_arguments_H #include <ctime> #include <set> class Human_being { private: tm _birth_date; public: Human_being(int = 1, int = 0, int = 1970 - 1900); private: std::set<Human_being*> _children; public: void births(Human_being*…); // '…' means that the number of arguments is variable (first style) void births(int, Human_being*…); // '…' means that the number of arguments is variable (second style) }; #endif /* _Variable_number_of_arguments_H */
// 'Main.cpp' file #include <cstdarg> // 'va_list' type #include <cstdlib> // 'NULL' #include "Variable_number_of_arguments.h" using namespace std; Human_being::Human_being(int day_of_month, int month, int year) { _birth_date.tm_mday = day_of_month; _birth_date.tm_mon = month; _birth_date.tm_year = year; } void Human_being::births(Human_being* child…) { // '…' means that the number of arguments is variable va_list children; va_start(children, child); for (Human_being* e = child; e != NULL; e = va_arg(children, Human_being*)) _children.insert(e); va_end(children); } void Human_being::births(int nb, Human_being* child…) { // '…' means that the number of arguments is variable va_list children; va_start(children, child); Human_being* e = child; for (int i = 0; i < nb; i++, e = va_arg(children, Human_being*)) _children.insert(e); va_end(children); } int main(int argc, char** argv) { Human_being FranckBarbier(11, 01, 1963); FranckBarbier.births(new Human_being(6, 12, 1993), new Human_being(15, 4, 1996), new Human_being(22, 2, 2001), NULL); // Alternative: // FranckBarbier.births(3, new Human_being(6, 12, 1993), new Human_being(15, 4, 1996), new Human_being(22, 2, 2001)); return 0; }
Rule(s)
- The
sizeof...
operator is a key C++ construct for the management of variadic templates.Example (definition as member function in
.h
file) Variadic.Cpp.zipclass Human_being { … public: // Must be 'public:' when called with one argument (direct call!): template <typename Child> void variadic_births(Child child) { // Base case... // Template member function in essence allows 'this': this->_children.insert(child); // This ends recursion... } template<typename Child, typename... Children> void variadic_births(Child child, Children... children) { // "Parameter pack" this->variadic_births(child); this->variadic_births(children...); // Unpack }; … };
Example (definition as non-member function in
.h
file) Variadic.Cpp.zipclass Human_being { … }; namespace variadic_template { template <typename T> void births(T& parent, T* child) { // Possible simplification: 'parent' and 'child' come from the same type // Constraints on 'T' is powered from C++20 (https://en.cppreference.com/w/cpp/language/constraints) static_assert(std::is_base_of<Human_being, T>::value, "Type parameter of this class must derive from 'Human_being'"); parent.insert(child); // Call of member function to access '_children'... } template<typename T, typename... Children> void births(T& parent, T* child, Children... children) { static_assert(sizeof... (Children) > 0, "Never inferred with '0' arguments, end of unpacking..."); // Constraints on 'T' is powered from C++20 (https://en.cppreference.com/w/cpp/language/constraints) static_assert(std::is_base_of<Human_being, T>::value, "Type parameter of this class must derive from 'Human_being'"); births(parent, child); births(parent, children...); } }
Example (usage in
.cpp
file) Variadic.Cpp.zipHuman_being s(28, 07, 1964); // s.variadic_births(); // As expected, compilation error... s.variadic_births(o.get(), l.get(), j.get()); std::cout << "s: " << s.count() << std::endl; // As non-member function: variadic_template::births(s, o.get(), l.get(), j.get()); // No effect because of 'std::set'
Variadic template and lambda expression
Example Lambda_expression.Cpp.zip
'.h' file: template<typename Cumulative, typename... Cumulatives> Cumulative Cumulate(Cumulative const& init, Cumulatives const&... cumulatives) { std::array<Cumulative, sizeof...(cumulatives) + 1 > my_array{init, cumulatives...}; // C++20: 'auto my_array = std::to_array(init,cumulatives...);' // Simple example of STL iterators' usage: return std::accumulate(std::next(my_array.begin()), my_array.end(), *my_array.begin()); } template<typename... Cumulatives> auto Cumulate_to_illustrate_lambda_expression(Cumulatives... cumulatives) { return [cumulatives...] () noexcept { return ::Cumulate(cumulatives...); }(); // Arrrggglll, immediate call... } /* Transform 'std::array' into pack... */ // C++14, 'std::index_sequence<I...> template<typename T, std::size_t N, std::size_t... I> auto _std__array_to_pack(const std::array<T, N>& my_array, std::index_sequence<I...>) { return ::Cumulate(my_array[I]...); } // C++14, 'std::make_index_sequence<N>' -> 0, 1... N-1 template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N> > auto std__array_to_pack(const std::array<T, N>& my_array) { return _std__array_to_pack(my_array, Indices()); } '.cpp' file: // 'é' as 'char' creates a (non-surprising) conflict between "Cumulative" and "Cumulatives" std::cout << "::Cumulate(std::string(1, e.character),\"é\",\"è\",\"ê\"): " // C++17 allows the inference of types between '<>': << ::Cumulate/*<std::string,std::string>*/(std::string(1, e.character), "é", "è", "ê") << std::endl; /* Test of transform 'std::array' into pack... */ std::array<std::string, 4U> my_array{{"e", "é", "è", "ê"}}; // Instantiation: T=std::string; N=4U; Indices=__make_integer_seq_alias<std::integer_sequence, unsigned int, 4U> std::cout << "std__array_to_pack(my_array): " << ::std__array_to_pack(my_array) << std::endl; // This calls '::Cumulate' std::cout << "Cumulate_to_illustrate_lambda_expression(std::string(1, e.character),\"é\",\"è\",\"ê\"): " << ::Cumulate_to_illustrate_lambda_expression(std::string(1, e.character), "é", "è", "ê") << std::endl;
Fold
Example Lambda_expression.Cpp.zip
template<typename Cumulative, typename... Cumulatives> Cumulative Cumulate(Cumulative const& init, Cumulatives const&... cumulatives) { std::array<Cumulative, sizeof...(cumulatives) + 1> my_array{init, cumulatives...}; // C++20: 'auto my_array = std::to_array(init,cumulatives...);' // C++17 fold: return (cumulatives + ... + init); }
See also
Rule(s)
- Concepts in C++20 are constraints on types. They are specifically useful for checking the mixing of inheritance and generics.
Example Vegetables.Cpp.zip
class Carot : public Vegetable { public: inline Color getColor() { return Color::Orange; } }; class Cosmic_carot : public Carot { public: inline Color getColor() { return Color::Purple; } }; template <typename T> concept Is_carot = std::is_base_of_v<Carot, T>; // <=> 'std::is_base_of<Carot, T>::value' // At most 2 kinds of carots in the garden: template<Is_carot... Carots> requires ((sizeof... (Carots) > 0 && sizeof... (Carots) <= 2)) class Carot_garden { /* ... */ }; template<typename Key_ingredient, typename... Ingredients> requires Is_carot<Key_ingredient> class Carot_cake { void cook(Key_ingredient carot, Ingredients... ingredients) { /* ... */ } }; class Cosmic_carot_cake : public Carot_cake<Cosmic_carot /*, ... */> {};
See also
Following case study is such one may have batavias, lettuces... in salad gardens while a batavia garden can have salads whose type is
Batavia
only. Functioning like comparing sizes of gardens, transferring salads… requires concept-based implementation.Example Vegetables.Cpp.zip
export module Garden; #include <deque> // C++ 23 is required to import the standard library as module... export import Vegetables; // "Vegetables" will be (also) imported for importers of 'Garden'... /* 'std::derived_from' is about *PUBLIC* inheritance */ export template<typename Specific_vegetable> requires std::derived_from<Specific_vegetable, Vegetable> class Garden { // Alternative syntax: 'export template<typename Specific_vegetable> class Garden requires std::derived_from<Specific_vegetable, Vegetable> {' private: std::deque<Specific_vegetable> _vegetables; public: const std::deque<Specific_vegetable>& vegetables() const { return _vegetables; } // Getter... bool is_bigger_than_V1(const Garden<Specific_vegetable>& garden) const { return this->_vegetables.size() > garden._vegetables.size(); } template <typename V> bool is_bigger_than_V2(const Garden<V>& garden) const requires std::derived_from<V, Specific_vegetable> { return this->_vegetables.size() > garden.vegetables().size(); } template <typename V> bool is_bigger_than_V3(const Garden<V>& garden) const requires std::derived_from<V, Specific_vegetable> || std::derived_from<Specific_vegetable, V> { return this->_vegetables.size() > garden.vegetables().size(); } void dig_out() { this->_vegetables.pop_back(); } void plant(const Specific_vegetable& specific_vegetable) { this->_vegetables.push_front(specific_vegetable); } int size() const { return this->_vegetables.size(); } Garden& operator=(Garden&& garden) noexcept { // Move assignment this->_vegetables.clear(); while (!garden._vegetables.empty()) { this->plant(garden._vegetables.back()); garden.dig_out(); } } template <typename V> void transfer(Garden<V>& garden) requires std::derived_from<V, Specific_vegetable> { this->_vegetables.clear(); while (!garden.vegetables().empty()) { this->plant(garden.vegetables().back()); garden.dig_out(); } } };
#include <iostream> import Garden; int main() { // Garden<Carot> gCarot; // Compilation error because 'Carot' hasn't been exported... Garden<Salad> gSalad; // Any kind of 'Salad' in the garden... Salad s; gSalad.plant(s); Garden<Batavia> gBatavia; // Only batavias... Batavia b; gBatavia.plant(b); // Compilation error because 'Garden<Salad>' and 'Garden<Batavia>' *ARE NOT* related to each other: // std::cout << std::boolalpha << gSalad.is_bigger_than_V1(gBatavia); // No compilation error because 'Batavia' inherits from 'Salad': std::cout << std::boolalpha << "'gSalad' size > 'gBatavia' size: " << gSalad.is_bigger_than_V2(gBatavia) << std::endl; // std::cout << std::boolalpha << gBatavia.is_bigger_than_V2(gSalad); // Unfortunately, (expected) compilation error... // No compilation error because 'Batavia' inherits from 'Salad' || 'Salad' inherits from 'Batavia': std::cout << std::boolalpha << "'gBatavia' size > 'gSalad' size: " << gBatavia.is_bigger_than_V3(gSalad) << std::endl; gSalad = Garden<Salad>(); // Move assignment std::cout << std::boolalpha << "'gSalad' size: " << gSalad.size() << std::endl; // Display is ''gSalad' size: 0' // Compilation error because move assignment deals with 'Garden<Salad>' only: // gSalad = Garden<Batavia>(); gSalad.transfer(gBatavia); std::cout << std::boolalpha << "'gSalad' size: " << gSalad.size() << std::endl; // Display is ''gSalad' size: 1' // Compilation error sounds nice since we don't want, for instance, lettuces in garden of batavias: // gBatavia.transfer(gSalad); return 0; }