C++:如何将任何可迭代类型作为函数参数传递
C++: How to pass any iterable type as a function parameter
作为练习,我尝试在 C++ 中实现 Python 的 str.join
方法。我最终会将该函数添加为 std::string
class 的方法,但我认为让它工作更重要。我定义函数如下:
template<typename Iterable>
std::string join(const std::string sep, Iterable iter);
有什么方法可以确保 Iterable 类型实际上是可迭代的?例如。我不想收到 int
或 char
..
在 C++ 中,我们没有一个 Iterable
,而是将一个迭代器(几乎是一个指针)传递给范围的前端和末尾:
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end);
请注意,sep
应作为 const reference 传递,因为您不需要复制它。
不过,您不必担心 Iter
是否实际上是一个迭代器。这是因为如果代码不起作用,将无法编译。
例如,假设您是这样实现的(这是一个糟糕的实现):
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end) {
std::string result;
while (begin != end) {
result += *begin;
++begin;
if (begin != end) result += sep;
}
return result;
}
然后作为 Iter
传入的类型必须有一个 operator++
、一个 operator!=
和一个 operator*
才能工作,这是一个很好理解的契约迭代器。
所有标准 c++ 集合都有 begin()
和 end()
成员函数。您可以利用该事实来确保传递的参数实际上是一些 SFINAE (c++11 示例)的集合(在您的术语中 - 可迭代):
#include <array>
#include <list>
#include <vector>
#include <map>
#include <string>
template <class Iterable>
auto join(const std::string sep, const Iterable& iterable) -> decltype(iterable.begin(), iterable.end(), std::string{}) {
(void)sep; // to suppress warning that sep isn't used
// some implementation
return {};
}
int main() {
join(";", std::array<int, 5>{});
join(";", std::list<int>{});
join(";", std::vector<float>{});
join(";", std::string{});
join(";", std::map<int, float>{});
//join(";", int{}); // does not compile as int is not a collection
}
您可以使用模板模板语法 - 如果需要 - 使用 SFINAE 来确保存在正确的 class 成员:
#include <vector>
#include <list>
#include <string>
#include <map>
#include <ostream>
//! Single value containers.
template< template<class> class L, class T,
class EntryT = typename L<T>::value_type>
std::string my_join(const std::string_view sep, const L<T>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry;
}
return ss.str();
}
//! std::map specialization - SFINAE used here to filter containers with pair value_type
template< template<class, class> class L, class T0, class T1,
class EntryT = typename L<T0, T1>::value_type,
class FirstT = typeof(EntryT::first),
class SecondT = typeof(EntryT::second)>
std::string my_join(const std::string_view sep, const L<T0, T1>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry.first << sep << entry.second;
}
return ss.str();
}
int main()
{
std::cout << my_join("; ", std::vector<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::list<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::string("1234")) << std::endl;
std::cout << my_join("; ", std::map<int, int>({ {1, 2}, {3, 4} })) << std::endl;
return 0;
}
// Output:
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
来自 https://devblogs.microsoft.com/oldnewthing/20190619-00/?p=102599 :
template<typename C, typename T = typename C::value_type>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
或者如果容器没有实现 value_type
:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
或者如果您只想要包含可转换为 int
的类型的容器:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>,
typename = std::enable_if_t<std::is_convertible_v<T, int>>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
但请参阅该博客中的评论 post 以获得更多改进。
作为练习,我尝试在 C++ 中实现 Python 的 str.join
方法。我最终会将该函数添加为 std::string
class 的方法,但我认为让它工作更重要。我定义函数如下:
template<typename Iterable>
std::string join(const std::string sep, Iterable iter);
有什么方法可以确保 Iterable 类型实际上是可迭代的?例如。我不想收到 int
或 char
..
在 C++ 中,我们没有一个 Iterable
,而是将一个迭代器(几乎是一个指针)传递给范围的前端和末尾:
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end);
请注意,sep
应作为 const reference 传递,因为您不需要复制它。
不过,您不必担心 Iter
是否实际上是一个迭代器。这是因为如果代码不起作用,将无法编译。
例如,假设您是这样实现的(这是一个糟糕的实现):
template<typename Iter>
std::string join(const std::string &sep, Iter begin, Iter end) {
std::string result;
while (begin != end) {
result += *begin;
++begin;
if (begin != end) result += sep;
}
return result;
}
然后作为 Iter
传入的类型必须有一个 operator++
、一个 operator!=
和一个 operator*
才能工作,这是一个很好理解的契约迭代器。
所有标准 c++ 集合都有 begin()
和 end()
成员函数。您可以利用该事实来确保传递的参数实际上是一些 SFINAE (c++11 示例)的集合(在您的术语中 - 可迭代):
#include <array>
#include <list>
#include <vector>
#include <map>
#include <string>
template <class Iterable>
auto join(const std::string sep, const Iterable& iterable) -> decltype(iterable.begin(), iterable.end(), std::string{}) {
(void)sep; // to suppress warning that sep isn't used
// some implementation
return {};
}
int main() {
join(";", std::array<int, 5>{});
join(";", std::list<int>{});
join(";", std::vector<float>{});
join(";", std::string{});
join(";", std::map<int, float>{});
//join(";", int{}); // does not compile as int is not a collection
}
您可以使用模板模板语法 - 如果需要 - 使用 SFINAE 来确保存在正确的 class 成员:
#include <vector>
#include <list>
#include <string>
#include <map>
#include <ostream>
//! Single value containers.
template< template<class> class L, class T,
class EntryT = typename L<T>::value_type>
std::string my_join(const std::string_view sep, const L<T>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry;
}
return ss.str();
}
//! std::map specialization - SFINAE used here to filter containers with pair value_type
template< template<class, class> class L, class T0, class T1,
class EntryT = typename L<T0, T1>::value_type,
class FirstT = typeof(EntryT::first),
class SecondT = typeof(EntryT::second)>
std::string my_join(const std::string_view sep, const L<T0, T1>& anyTypeIterable)
{
std::stringstream ss;
bool first = true;
for (const EntryT& entry : anyTypeIterable)
{
if (first) first = false;
else ss << sep;
ss << entry.first << sep << entry.second;
}
return ss.str();
}
int main()
{
std::cout << my_join("; ", std::vector<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::list<int>({1, 2, 3, 4})) << std::endl;
std::cout << my_join("; ", std::string("1234")) << std::endl;
std::cout << my_join("; ", std::map<int, int>({ {1, 2}, {3, 4} })) << std::endl;
return 0;
}
// Output:
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
// 1; 2; 3; 4
来自 https://devblogs.microsoft.com/oldnewthing/20190619-00/?p=102599 :
template<typename C, typename T = typename C::value_type>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
或者如果容器没有实现 value_type
:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
或者如果您只想要包含可转换为 int
的类型的容器:
template<typename C, typename T = std::decay_t<decltype(*begin(std::declval<C>()))>,
typename = std::enable_if_t<std::is_convertible_v<T, int>>>
auto do_something_with(C const& container)
{
for (int v : container) { ... }
}
但请参阅该博客中的评论 post 以获得更多改进。