2016-11-18 44 views
9

如何编写一个概念来描述基于范围for循环的类型?如何编写简单的范围概念?

一个尝试是:

template < typename Range > concept bool RRange 
    = requires(Range range) {{std::begin(range),std::end(range)};}; 

但我真正想要的是一些这样的事:

template < typename Range > concept bool RRange 
    = requires(Range range) {{for(auto&& item : range);};}; // compile error 

就是RRange是所有类型的概念表达for(auto&& item : range);是有效的。达到此目的的最佳方法是什么?

我使用GCC7快照与g++ -std=c++1z -fconcepts

回答

1

这是我在审查[stmt.ranged]时想到的。

#include <utility> 
#include <experimental/type_traits> 

template <class T> using begin_non_mf_t = decltype(begin(std::declval<T>())); 
template <class T> using begin_mf_t  = decltype(std::declval<T>().begin()); 
template <class T> using begin_t  = decltype(T::begin); 
template <class T> using end_non_mf_t = decltype(end(std::declval<T>())); 
template <class T> using end_mf_t  = decltype(std::declval<T>().end()); 
template <class T> using end_t   = decltype(T::end); 

template <class T> 
constexpr bool has_member_begin_or_end { 
    std::experimental::is_detected_v<begin_mf_t,T> || 
    std::experimental::is_detected_v<begin_t,T> || 
    std::experimental::is_detected_v<end_mf_t,T> || 
    std::experimental::is_detected_v<end_t,T>}; 

template <class T> 
std::add_lvalue_reference_t<T> declref() noexcept; 
template <class T> using declref_t = decltype(declref<T>()); 

template <class T> 
concept bool Range = 
    requires /*Arrays*/ { 
     requires std::is_array_v<T>; 
     requires std::extent_v<T>!=0; // Extent is known. 
    } || 
    /*Classes with member begin/end*/ 
    requires { 
     requires std::is_class_v<T> && has_member_begin_or_end<T>; 
    } && 
    requires (begin_mf_t<declref_t<T>> _begin, 
       end_mf_t<declref_t<T>> _end) { 
     { _begin!=_end } -> bool; 
     { *_begin } -> auto&&; 
     { ++_begin }; 
    } || 
    /*Types with non-member begin/end*/ 
    requires { 
     requires !std::is_class_v<T> || !has_member_begin_or_end<T>; 
    } && 
    requires (begin_non_mf_t<declref_t<T>> _begin, 
       end_non_mf_t<declref_t<T>> _end) { 
     { _begin!=_end } -> bool; 
     { *_begin } -> auto&&; 
     { ++_begin }; 
    }; 

和测试用例。

#include <vector> 

// Evaluates to true or diagnoses which constraints failed. 
template <Range> constexpr bool is_range {true}; 

static_assert(!Range<void>); 
static_assert(!Range<int>); 
static_assert(!Range<int*>); 
static_assert(!Range<int[]>); 
static_assert(is_range<int[1]>); 
static_assert(is_range<std::vector<int>>); 

struct A { }; 
struct B { 
    int begin; 
}; 
struct C { 
    int* begin(); 
    int* end(); 
}; 
struct D { }; 
struct E { 
    int end; 
}; 
enum F { }; 
struct G { 
    int* begin() &&; 
    int* end(); 
}; 
struct H { 
    int* begin() &&; 
    int* end() &&; 
}; 
int* begin(D); 
int* end(D); 
int* begin(E); 
int* end(E); 
int* begin(F); 
int* end(F); 
int* begin(H); 
int* end(H); 

static_assert(!Range<A>); 
static_assert(!Range<B>); 
static_assert(is_range<C>); 
static_assert(is_range<D>); 
static_assert(!Range<E>); 
static_assert(is_range<F>); 
static_assert(!Range<G>); 
static_assert(!Range<H>); 

int main() { } 
+1

这是很好的一个警告补充说,由于该规范是怎么写的,用户代码只能让在概念ifying尽力尝试这种语言功能(虽然非常好)。有(非常)病理边缘病例不能被发现。 –

+0

@LucDanton我已经重写了这个概念,以便对命名变量进行操作,正如range-for语义所要求的那样。但是,增加的测试用例失败了,我不知道为什么。这可能是一个边缘案例,就像你之前提到的那样? –

+0

我不应该用以前的解决方案跳过测试新的测试用例。我已经通过添加的'declref'间接来修复它。谢谢你为我做的一切。 –

0

根据P0587,这应该足够了:

#include <vector> 

template<typename T> 
concept bool RangeForAble = requires (T t) { 
    requires requires (decltype(begin(t)) b, decltype(end(t)) e) { 
    b != e; 
    ++b; 
    *b; 
    }; 
}; 

int main() 
{ 
static_assert(RangeForAble<std::vector<int>>); 
static_assert(RangeForAble<double>); 
}