有什么方法可以检查任意变量类型是否可迭代?
那么要检查它是否有索引元素,或者我实际上可以循环它的子元素? (例如使用foreach?)
是否可以为此创建一个通用模板?
我在搜索时发现了其他编程语言的技术。但仍然需要找出如何在 C++ 中做到这一点。
你可以为此创建一个特质:
namespace detail
{
// To allow ADL with custom begin/end
using std::begin;
using std::end;
template <typename T>
auto is_iterable_impl(int)
-> decltype (
begin(std::declval<T&>()) != end(std::declval<T&>()), // begin/end and operator !=
void(), // Handle evil operator ,
++std::declval<decltype(begin(std::declval<T&>()))&>(), // operator ++
void(*begin(std::declval<T&>())), // operator*
std::true_type{});
template <typename T>
std::false_type is_iterable_impl(...);
}
template <typename T>
using is_iterable = decltype(detail::is_iterable_impl<T>(0));
实例.
cpprefence 有一个回答您问题的示例。它使用 SFINAE,这是该示例的稍微修改的版本(以防该链接的内容随着时间的推移而改变):
template <typename T, typename = void>
struct is_iterable : std::false_type {};
// this gets used only when we can call std::begin() and std::end() on that type
template <typename T>
struct is_iterable<T, std::void_t<decltype(std::begin(std::declval<T&>())),
decltype(std::end(std::declval<T&>()))
>
> : std::true_type {};
// Here is a helper:
template <typename T>
constexpr bool is_iterable_v = is_iterable<T>::value;
现在,这就是它的使用方法
std::cout << std::boolalpha;
std::cout << is_iterable_v<std::vector<double>> << '\n';
std::cout << is_iterable_v<std::map<int, double>> << '\n';
std::cout << is_iterable_v<double> << '\n';
struct A;
std::cout << is_iterable_v<A> << '\n';
输出:
true
true
false
false
话虽如此,它检查的只是
begin() const
和 end() const
的声明,因此,即使以下内容也被验证为可迭代:
struct Container
{
void begin() const;
void end() const;
};
std::cout << is_iterable_v<Container> << '\n'; // prints true
您可以在这里
一起看到这些作品如果您使用 C++11 及更高版本,当您必须专门针对一个属性时,SFINAE 检查的一种常用方法是以下一种:
template<class T, class = decltype(<expression that must compile>)>
inline constexpr bool expression_works(int) { return true; }
template<class>
inline constexpr bool expression_works(unsigned) { return false; }
template<class T, bool = expression_works<T>(42)>
class my_class;
template<class T>
struct my_class<T, true>
{ /* Implementation when true */ };
template<class T>
struct my_class<T, false>
{ /* Implementation when false */ };
窍门如下:
false
。42
的类型为 int
,因此 int
比 unsigned
更好匹配,得到 true
。42
,因为它是“一切问题的答案”,受到 Eric Niebler 范围实现的启发。
C++11
具有适用于数组和容器的自由函数
std::begin
和std::end
,因此必须有效的表达式是:template<class T, class = decltype(std::begin(std::declval<T>()))
inline constexpr bool is_iterable(int) { return true; }
template<class>
inline constexpr bool is_iterable(unsigned) { return false; }
如果您需要更多通用性,表达某些内容可迭代的方法还可以包括用户定义的类型,这些类型为
begin
和
end
带来自己的重载,因此您需要在此处应用一些 adl
:namespace _adl_begin {
using std::begin;
template<class T>
inline auto check() -> decltype(begin(std::declval<T>())) {}
}
template<class T, class = decltype(_adl_begin::check<T>())>
inline constexpr bool is_iterable(int) { return true; }
template<class>
inline constexpr bool is_iterable(unsigned) { return false; }
您可以使用此技术来实现更适合您实际情况的解决方案。
使用这个traits类兼容c++03
template<typename C>
struct is_iterable
{
typedef long false_type;
typedef char true_type;
template<class T> static false_type check(...);
template<class T> static true_type check(int,
typename T::const_iterator = C().end());
enum { value = sizeof(check<C>(0)) == sizeof(true_type) };
};
说明
check<C>(0)
check(int,const_iterator)
调用 C::end()
并返回 const_iterator
兼容类型else check<C>(0)
check(...)
(请参阅省略号转换)
sizeof(check<C>(0))
value
true
或 false
#include <iostream>
#include <set>
int main()
{
std::cout <<"set="<< is_iterable< std::set<int> >::value <<'\n';
std::cout <<"int="<< is_iterable< int >::value <<'\n';
}
输出
set=1
int=0
C++11(和 C++14)提供了许多 traits 类,但没有提供可迭代性... 另请参阅
jrok和 Jarod42 的类似答案。
此答案属于公共领域 -如果通过
foreach
您指的是 C++11 基于范围的 for 循环,则该类型需要定义
begin()
和 end()
方法并返回响应 operator!=
的迭代器,
operator++
和 operator*
。如果您指的是 Boost 的 BOOST_FOREACH 助手,请参阅 BOOST_FOREACH 扩展性
。 如果在您的设计中您有一个所有可迭代容器都继承自的通用接口,那么您可以使用 C++11 的
std::is_base_of:
struct A : IterableInterface {}
struct B {}
template <typename T>
constexpr bool is_iterable() {
return std::is_base_of<IterableInterface, T>::value;
}
is_iterable<A>(); // true
is_iterable<B>(); // false
::type
和
::value
废话的虚拟结构定义,那么这里有一个使用快速且(非常)肮脏的单行代码的示例:template <
class Container,
typename ValueType = decltype(*std::begin(std::declval<Container>()))>
static void foo(Container& container)
{
for (ValueType& item : container)
{
...
}
}
最后一个模板参数一步完成多项操作:
检查类型是否具有
begin()
begin()
operator*()
的内容(迭代器的典型情况)。确定取消引用迭代器所产生的类型,并保存它,以防它在模板实现中有用。end()
成员函数。
如果您想要更强大/彻底/可重用的东西,那么请选择其他优秀的建议解决方案之一。