Tökéletes függvény wrap

C++programozás

Kísérletezgetek mostanában egy olyan C++ template létrehozásával, amely a következőket tudja:

  • Nem számít, hogy milyen paraméterlistája van a hívható objektumnak/függvénynek
  • Megőrzi a hívható objektum/függvény visszatérési értékét és továbbítja
  • Tetszőleges elő- és utóhívást burkolhatunk rá az adott hívható elemre
  • Az elő- és utóhívások egymásba is burkolhatóak több elő-utó pár megadásával
  • Működik függvényekre
  • Műküdik funktorokra (const correctnessel)
  • Működik lambdákra
  • Ha egy funktornak több overloadja van, mindegyiket támogatja ugyanaz a wrap objektum egyszerre
  • Kivételbiztos: Ha az előhívó lefutott, akkor a megfelelő utóhívás is garantáltan lefut (kivétel esetén is)
  • A léterhozónak nem kell típusokkal bíbelődni, a wrap kitalál mindent
  • A használónak sem kell (feltétlenül) tudnia, hogy egy wrap-ről van szó

Az alapgondolat az, hogy adott valami, amit legalább egyféleképp meg lehet hívni, és visszatér valamivel. Ezt a valamit szeretném beburkolni úgy, hogy kapjak egy funktort, amit ugyanúgy tudok hívni, mint azt, amit beburkoltam, viszont ha meghívom, akkor a tényleges hívás előtt és a hívás után is lefutnak függvénypárok (elő- és utóhívások), amelyeket a funktor létrehozásakor adtam meg.

A jelenlegi implementációm kötöttsége, hogy az elő- és az utóhívók csak void(void) szignatúrájúak lehetnek. Ezen felül nem vagyok benne biztos, hogy minimalizáltam az előforduló objektum-másolások számát.

A használat a következő:

// ... make_wrap implementációja

// Ez csak egy peldafuggveny
int
foo(int x) {

	cout << x << endl;
	return x;
}

// Ez csak egy peldaosztaly...
struct simple_functor {

	void
	operator()(void) {

		cout << "simple_functor (" << this << ") non-const" << endl;
	}


	void
	operator()(void) const {

		cout << "simple_functor (" << this << ") const" << endl;
	}


	int
	operator()(int x) {

		cout << "simple_functor (" << this << ") called with " << x << endl;
		return 2*x;
	}

};

int main() {

	simple_functor s1;
	const simple_functor s2;

	auto pre = [](){ cout << "Pre" << endl; }; // elulso burkolo fuggveny
	auto post = [](){ cout << "Post" << endl; }; // hatso burkolo fuggveny
	auto func = [](int x) -> int { return 2*x; };

	auto w1 = make_wrap(func, pre, post, pre, post, pre, post);

        // a ref azert kell, hogy referenciat mentsen s1-rol, ne masolja.
	auto w2 = make_wrap(ref(s1), pre, post);
	auto w3 = make_wrap(ref(s2),
            [](){ cout << "Haha" << endl; },
            [](){ cout << "Hehe" << endl; }
        );

	// Wrap hivasok!
	cout << w1(2) << endl;
	w2();
	w3();
	cout << w2(3) << endl;
	return 0;
}

És az implementáció:

#include <iostream>
#include <functional>


using namespace std;


template<typename POST>
struct wrap_raii final {

	POST& p_;

	~wrap_raii() {

		p_();
	}
};


template<typename FUNC, typename PRE1, typename POST1, typename... MORE>
class wrap final {

	private:

		wrap<FUNC, MORE...> inner_wrap_;

		PRE1 pre1_;

		POST1 post1_;

	public:

		wrap(FUNC f, PRE1 pre1, POST1 post1, MORE&&... more_args)
		: inner_wrap_(std::forward<FUNC>(f),
                      std::forward<MORE>(more_args)...),
		  pre1_(std::move(pre1)),
		  post1_(std::move(post1)) {
		}


		template<typename... Args>
		auto
		operator()(Args&&... args)
		  -> decltype(inner_wrap_(std::forward<Args>(args)...)) const {

			pre1_();
			wrap_raii<POST1> w_raii{ post1_ };
			return inner_wrap_(std::forward<Args>(args)...);
		}

};


template<typename FUNC, typename PRE, typename POST>
class wrap<FUNC,PRE,POST> final {

	private:

		FUNC f_;

		PRE pre_;

		POST post_;

	public:

		wrap(FUNC f, PRE pre, POST post) : f_(f), pre_(pre), post_(post) {
		}


		template<typename... Args>
		auto
		operator()(Args&&... args)
		  -> decltype(f_(std::forward<Args>(args)...)) const {

			pre_();
			wrap_raii<POST> w_raii{ post_ };
			return f_(std::forward<Args>(args)...);
		}

};


template<typename FUNC, typename PRE, typename POST>
wrap<FUNC,PRE,POST>
make_wrap(FUNC f, PRE pre, POST post) {

	return wrap<FUNC,PRE,POST>(
		std::forward<FUNC>(f),
            std::forward<PRE>(pre),
            std::forward<POST>(post)
	);
}


template<typename FUNC, typename PRE1, typename POST1,
         typename PRE2, typename POST2, typename... MORE>
wrap<FUNC, PRE1, POST1, PRE2, POST2, MORE...>
make_wrap(FUNC f, PRE1 pre1, POST1 post1, PRE2 pre2, POST2 post2,
		MORE&&... more_args) {

	return wrap<FUNC,PRE1,POST1,PRE2,POST2,MORE...>(
				std::forward<FUNC>(f),
				std::forward<PRE1>(pre1),
				std::forward<POST1>(post1),
				std::forward<PRE2>(pre2),
				std::forward<POST2>(post2),
				std::forward<MORE>(more_args)...
			);
}

Remélem ezzel már közelítek a fent leírt céljaim felé...