2016-11-18 30 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>); 
}