函数的二义性与函数对象的传递问题(通过实现vector的to_string示例)

许多时候,我们想要直接打印容器的内容,比如

std::vector<int> a = { 1, 2, 3 };

可以打印出[1, 2, 3]。

 

参考标准库,可以写出一个带有迭代器的to_string函数:

template <typename Iter, typename Func>
std::string to_string(Iter begin, Iter end, Func func) {
	std::string str;
	str.append("[");
	if (begin != end) {
		str.append(func(*begin++));
		while (begin != end) {
			str.append(", ");
			str.append(func(*begin++));
		}
	}
	str.append("]");
	return str;
}

在调用时候,可以直接传入begin()和end(),作为迭代器,再传入对于元素的to_string函数,即可实现容器的to_string。

std::to_string是C++11新增的标准库中定义的函数,具有多个重载函数(详情请参考to_string - C++Reference):

string to_string (int val);
string to_string (long val);
string to_string (long long val);
string to_string (unsigned val);
string to_string (unsigned long val);
string to_string (unsigned long long val);
string to_string (float val);
string to_string (double val);
string to_string (long double val);

由于是重载函数,因此不能直接使用名称to_string进行函数的传递。

我们想要使用类似于to_string(vec.begin(), vec.end(), to_string)这种传递方式,怎么办呢?

 

在C++11中,我们可以传递匿名函数(lambda)解决二义性问题:

用 [](const T &t) { return to_string(t); } 取代 to_string。

to_string(vec.begin(), vec.end(), [](const T &t) { return to_string(t); });

还有一种方法,就是定义函数对象:

template <typename T>
struct ToString
{
	std::string operator()(const T &t)
	{
		return to_string(t);
	}
};

to_string(vec.begin(), vec.end(), ToString<T>());

这在C++11标准之前的代码中经常使用。

 

借此,我们可以定义一个vector版的to_string函数:

template <typename T>
std::string to_string(const std::vector<T> &vec)
{
	return to_string(vec.begin(), vec.end(),[](const T &t) { return to_string(t); });
}
// Or
template <typename T>
std::string to_string(const std::vector<T> &vec)
{
	return to_string(vec.begin(), vec.end(), ToString<T>());
}

然后,就可以这么使用了:

	std::vector<int> a = { 1, 2, 3 };
	std::vector<int> b = { 4, 5, 6 };
	std::vector<std::vector<int>> c = { a, b };

	std::cout << to_string(a) << std::endl; // [1, 2, 3]
	std::cout << to_string(b) << std::endl; // [4, 5, 6]
	std::cout << to_string(c) << std::endl; // [[1, 2, 3], [4, 5, 6]]

可以输出vector的各种类型(包括vector)。

 

然而,存在一个问题就是,由于我的这些代码在最初就using namespcae std;了,因此并没有什么问题出现(很好地实现了vector类型的to_string重载)。

但是,当我们把该行去掉后,就会发现,编译器会报出错误。

比如在ToString类的operator()函数中,在

return to_string(t);

这一行,就报出了:

error: no matching function for call to 'to_string(const int&)'

的错误。

似乎解决方法很简单,直接加std::不就行了吗?

然而,我们发现,这种解决方法,在只输出a和b的情况下,确实没什么问题,但是,当输出c的时候,又报出了

error: no matching function for call to 'to_string(const std::vector<int>&)'

的错误。

——怎么办呢?

其实解决起来很简单,就是使用using使得命名空间无效,当然这个using是加在函数里面的。

template <typename T>
struct ToString
{
	std::string operator()(const T &t)
	{
		using std::to_string; // 标准库to_string
		using ::to_string;    // 自定义to_string
		
		return to_string(t);
	}
};

using ::to_string表示你自定义to_string的位置,如果也是在命名空间里面,如(namespace A),那么就写成using A::to_string;即可。

 

完整代码如下:

#include <iostream>
#include <string>
#include <vector>

template <typename Iter, typename Func>
std::string to_string(Iter begin, Iter end, Func func) {
	std::string str;
	str.append("[");
	if (begin != end) {
		str.append(func(*begin++));
		while (begin != end) {
			str.append(", ");
			str.append(func(*begin++));
		}
	}
	str.append("]");
	return str;
}

template <typename T>
struct ToString;

template <typename T>
std::string to_string(const std::vector<T> &vec)
{
	return to_string(vec.begin(), vec.end(), ToString<T>());
	// Or
	/*
	return to_string(vec.begin(), vec.end(),[](const T &t) {
		using std::to_string;
		using ::to_string;

		return to_string(t);
	});
	*/
}

template <typename T>
struct ToString
{
	std::string operator()(const T &t)
	{
		using std::to_string;
		using ::to_string;

		return to_string(t);
	}
};

int main(void)
{
	std::vector<int> a = { 1, 2, 3 };
	std::vector<int> b = { 4, 5, 6 };
	std::vector<std::vector<int>> c = { a, b };

	std::cout << to_string(c) << std::endl;
	return 0;
}

 

转载于:https://www.cnblogs.com/chillmagic/p/5713773.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值