C++11 wrapper (function)

๐ŸŒˆForeword

This article learns about wrappers in C++11! ! !

๐Ÿš1. Wrapper

๐Ÿš‚1.1, function wrapper

function wrappers are also called adapters. A function in C++ is essentially a class template and a wrapper

Test = func(x);
  • What could the above func be?

  • func may be a function name? function pointer? Function object (functor object)? Or a lamber expression object?

  • These are all callable types! Such a rich type, if passed to the template, may lead to inefficiency of the template!

namespace FC
{
	// Wrapper chapter: A class template may instantiate multiple copies of the same code at the same time, and different types call the same version of the function, resulting in low efficiency
	// This type may be a function pointer, functor, lambda expression, etc....
	template <typename F, typename T>
	T useF(F f, T t)
	{
		static int Count = 0;
		cout << "Count: " << ++Count << endl;
		cout << "Count address: " << &Count << endl;

		return f(t);
	}

	double f(double x)
	{
		return x / 2;
	}

	struct Func
	{
		double operator()(double x)
		{
			return x / 3;
		}
	};

	void Test()
	{
		// function pointer
		cout << useF(f, 1.2) << endl;

		// functor (function object)
		cout << useF(Func(), 1.2) << endl;

		// lambda expression
		cout << useF([](double x)->double { return x / 4; }, 1.2);
	}
}

Through the above program verification, we will find that the useF function template instantiates three different codes

The wrapper can solve the above problem very well:
https://legacy.cplusplus.com/reference/functional/function/?kw=function

// The class template prototype is as follows
template <class T> function; // undefined

template <class Ret, class... Args>
class function<Ret(Args...)>;

Template parameter description:

  • The std::function class template is defined in the functional header file

  • Ret: the return type of the called function

  • Args...: list of arguments to the called function

// The method of use is as follows:
#include <functional>
int f(int a, int b)
{
	return a + b;
}

struct Functor
{
public:
	int operator() (int a, int b)
	{
		return a + b;
	}
};

int main()
{
	// function name (function pointer)
	std::function<int(int, int)> func1 = f;
	cout << func1(1, 2) << endl;
	
	// function object
	std::function<int(int, int)> func2 = Functor();
	cout << func2(1, 2) << endl;
	
	// lamber expression
	std::function<int(int, int)> func3 = [](const int a, const int b)
	{
		return a + b; 
	};
	cout << func3(1, 2) << endl;
}

You can also wrap class static member functions and non-static member functions:

class T
{
public:
	static int Ti(int x, int y)
	{
		return x + y;
	}

	int Td(int x, int y)
	{
		return x - y;
	}
};
	
void Test()
{
	// &T::Ti/T().Ti -- static member function, does not contain hidden this pointer
	std::function<int(int, int)> f4 = T().Ti; 
	cout << f4(1, 2) << endl;
	
	// The member function contains a hidden this pointer, and the formal parameter needs to define an additional type
	std::function<int(T, int, int)> f5 = &T::Td; 
	cout << f5(T(), 10, 5) << endl;
}

Notice:

  • When wrapping a static member in a class, you can do not take the address of its function name (T::Ti), or you can directly call the assignment anonymously (T().Ti)

  • However, when wrapping member functions in a class, because a this pointer is hidden in the member functions, we need to display a class when defining parameters, otherwise an error will be reported

  • When wrapping the member function, the rvalue can only be the name of the member function that takes the address, and it may be that the member function can only be called through it.

With the wrapper, how to solve the problem of inefficient templates and multiple instantiations?

namespace FC
{
	// Wrapper chapter: A class template may instantiate multiple copies of the same code at the same time, and different types call the same version of the function, resulting in low efficiency
	// This type may be a function pointer, functor, lambda expression, etc....
	template <typename F, typename T>
	T useF(F f, T t)
	{
		static int Count = 0;
		cout << "Count: " << ++Count << endl;
		cout << "Count address: " << &Count << endl;

		return f(t);
	}

	double f(double x)
	{
		return x / 2;
	}

	struct Func
	{
		double operator()(double x)
		{
			return x / 3;
		}
	};

	void Test()
	{
		std::function<double(double)> f1 = f;
		cout << useF(f1, 1.2) << endl;

		std::function<double(double)> f2 = Func();
		cout << useF(f2, 1.2) << endl;

		std::function<double(double)> f3 = [](double x)->double { return x / 4; };
		cout << useF(f3, 1.2) << endl;
	}
}

Some other scenarios for wrappers:

https://leetcode.cn/problems/evaluate-reverse-polish-notation/submissions/

// How to play after using the wrapper
class Solution {
public:
	int evalRPN(vector<string>& tokens) {
		stack<int> st;
		map<string, function<int(int, int)>> opFuncMap =
		{
		{ "+", [](int i, int j) {return i + j; } },
		{ "-", [](int i, int j) {return i - j; } },
		{ "*", [](int i, int j) {return i * j; } },
		{ "/", [](int i, int j) {return i / j; } }
		};
		for (auto& str : tokens)
		{
			if (opFuncMap.find(str) != opFuncMap.end())
			{
				int right = st.top();
				st.pop();
				int left = st.top();
				st.pop();
				st.push(opFuncMap[str](left, right));
			}
			else
			{
				// 1,atoi itoa
				// 2,sprintf scanf
				// 3,stoi to_string C++11
				st.push(stoi(str));
			}
		}
		return st.top();
	}
};

๐Ÿšƒ2,bind

  • The std::bind function is defined in the header file and is a function template. It is like a function wrapper (adapter) that accepts a callable object and generates a new callable object to "fit" the original object. parameter list of

  • In general, we can use it to take a function fn that originally received N parameters, and return a new function that receives M parameters (M can be greater than N, but it does not make sense to do so) by binding some parameters.

  • At the same time, the use of std::bind function can also achieve parameter order adjustment and other operations

// The prototype is as follows:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

The bind function can be thought of as a generic function adapter that takes a callable object and generates a new callable object to "fit" the original object's parameter list

The general form of calling bind: auto newCallable = bind(callable,arg_list);

  • Among them, newCallable itself is a callable object, and arg_list is a comma-separated list of parameters corresponding to the parameters of the given callable. When we call newCallable, newCallable will call callable and pass it the parameters in arg_list

  • The arguments in arg_list may contain names of the form _n, where n is an integer, and these arguments are "placeholders" representing the arguments to newCallable that occupy the "positions" of the arguments passed to newCallable.

  • The value n represents the position of the parameter in the generated callable: _1 is the first parameter of newCallable, _2 is the second parameter, and so on

  • bind can also change the order of the parameter list of the original object, for example: _2 is the first parameter of newCallable, _1 is the second parameter, then when passing parameters, it must be passed in reverse.

// Example of use
int Plus(int a, int b)
{
	return a + b;
}
class Sub
{
public:
	int sub(int a, int b)
	{
		return a - b;
	}
};

int main()
{
	//Indicates that the plus parameter of the bound function is specified by the first and second parameters of calling func1 respectively
	std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,placeholders::_2);

	//auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
	//The type of func2 is function<void(int, int, int)> which is the same type as func1
	
	//Represents the first and second of the binding function plus: 1, 2
	auto func2 = std::bind(Plus, 1, 2);
	cout << func1(1, 2) << endl;
	cout << func2() << endl;

	Sub s;
	// bind member function
	std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,
		placeholders::_1, placeholders::_2);

	// Parameter swap order
	std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,
		placeholders::_2, placeholders::_1);
	cout << func3(1, 2) << endl;
	cout << func4(1, 2) << endl;
	return 0;
}

It can also be used like this:

namespace Bind
{
	// Wrapper chapter: A class template may instantiate multiple copies of the same code at the same time, and different types call the same version of the function, resulting in low efficiency
	// This type may be a function pointer, functor, lambda expression, class member function, etc....
	int f(int a, int b)
	{
		return a + b;
	}

	struct Functor
	{
	public:
		int operator() (int a, int b)
		{
			return a + b;
		}
	};

	class T
	{
	public:
		int Ti(int x, int y)
		{
			return x + y;
		}
	};

	void Test()
	{
		map<string, std::function<int(int, int)>> m
		{
			{ "function pointer", f },
			{ "functor", Functor() },
			{ "member function", std::bind(&T::Ti, T(), placeholders::_1, placeholders::_2) }
		};

		// [] returns the second(function itself) in pair<string, function>, you can directly find the corresponding key through [] and call it
		cout << m["function pointer"](1, 2) << endl;
		cout << m["functor"](10, 20) << endl;
		cout << m["member function"](100, 200) << endl;
	}
}

Tags: C++ programming language

Posted by xpressmail on Mon, 03 Oct 2022 03:52:47 +1030