C++ Variadics & Concepts





This -C++ Variadics & Concepts- tutorial is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License
Headlines
Variadic function

Rule(s)

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;
}
Variadic template

Rule(s)

Example (definition as member function in .h file) Variadic.Cpp.zip 

class 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.zip 

class 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.zip 

Human_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

Concepts

Rule(s)

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;
}