Ulfs Blog

14.3.2020, 20:30

Lambdas in C++ überladen

Ulfs Blog

Mit den Concepts und weiteren Änderungen in C++17 und C++20 gibt es die Möglichkeit Funktionen ein Lambda zurückgeben zu lassen (Concepts im Folgendem nicht nach C++20, sondern gemäß der GCC Implementierung):

// helper to have a lambda with correct parameters in concepts
template<class... ParameterT>
struct declare_void_lambda
{
	void operator()(ParameterT...) {}
};

// concept which matches containers with a
// for_each(const value_type&, size_t) const
// method
template<class T>
concept bool ForEachable = requires(
	const T& t,
	declare_void_lambda<const typename T::value_type&, size_t> lambda)
{
	typename T::value_type;
	{ t.for_each(lambda) } -> void;
};
	
// functor 
auto functor_for_each(auto lambda)
{
	return [lambda](const ForEachable& t) -> void
	{
		t.for_each([lambda](
			const std::remove_cvref_t<decltype(t)>::value_type& v,
			size_t i)
		{
			/* do something */
			lambda(v, i);
		}
	}
}

Solche Funktoren können sehr praktisch sein. Ebenfalls praktisch wäre es, wenn man das zurückgegebene Lambda überladen könnte. Bekanntlich ist ein Lambda nur eine kompilergenerierte, anonyme Klasse mit einem passenden operator().

Martin Ecker hat auf seinem Blog die Lösung dafür geschrieben: Man braucht eine Klasse, die von allen zu überladenden Lambdas ableitet. Mit C++17 sieht die Lösung noch etwas kürzer aus. Auch die Funktion overload können wir weglassen, weil der Konstruktoraufruf gleich aussieht:

template<class... Lambdas>
struct overload_set : Lambdas...
{
	// constructor
	overload_set(Lambdas&&... lambdas)
		: Lambdas(std::forward<Lambdas>(lambdas))... {}
	
	// make all operator() visible
	using Lambdas::operator()...;
};

Damit lässt sich die Funktorenmethode aus dem Beispiel mit einem nicht konstanten for_each überladen:

// concept which matches containers with a 
// for_each(value_type&, size_t)
// method
template<class T>
concept bool NonConstForEachable = requires(
	T& t, 
	declare_void_lambda<typename T::value_type&, size_t> lambda)
{
	typename T::value_type;
	{ t.for_each(lambda) } -> void;
};
	
// functor
auto functor_for_each(auto lambda)
{
	return overload_set
	{
		[lambda](const ForEachable& t) -> void
		{
			t.for_each([lambda](
				const std::remove_cvref_t<decltype(t)>
					::value_type& v,
				size_t i)
			{
				/* do something */
				lambda(v, i);
			}
		},
		[lambda](NonConstForEachable& t) -> void
		{
			t.for_each([lambda](
				std::remove_cvref_t<decltype(t)>::value_type& v,
				size_t i)
			{
				/* do something */
				lambda(v, i);
			}
		},
	};
}