考虑基于范围的for循环 begin-expr 和 end-expr (N4140 [stmt.ranged] / p1)的规范。给定__range
类型_RangeT
的范围,
begin-expr 和 end-expr 的确定如下:
- 如果
_RangeT
是数组类型,则 begin-expr 和 end-expr 分别为__range
和__range + __bound
,__bound
的位置 数组绑定。如果_RangeT
是未知大小的数组或数组 不完整的类型,该程序是不正确的;- 如果
_RangeT
是类类型,则会在类begin
的范围内查找 unqualified-id send
和_RangeT
好像通过类成员访问 查找(3.4.5),如果其中任何一个(或两个)找到至少一个 声明, begin-expr 和 end-expr 是__range.begin()
和 分别为__range.end()
;- 否则, begin-expr 和 end-expr 分别为
begin(__range)
和end(__range)
,其中begin
和{{ 1}}被查找 关联的命名空间(3.4.2)。 [注意:普通不合格 查找(3.4.1)未执行。 - 结束记录]
是否可以在普通C ++代码中模拟此精确行为?也就是说,我们可以编写end
和magic_begin
函数模板,以便
magic_end
和
for(auto&& p : range_init) { /* statements */ }
总是有完全相同的行为?
非答案包括对{
auto&& my_range = range_init;
for(auto b = magic_begin(my_range), e = magic_end(my_range); b != e; ++b){
auto&& p = *b;
/* statements */
}
}
/ std::begin
的合格调用(除其他事项外不处理第三个项目符号)和std::end
,因为除其他事项外,如果不明确的话using std::begin; begin(range);
的ADL发现一个与begin
一样好的重载。
为了说明,给出
std::begin
我希望namespace foo {
struct A { int begin; };
struct B { using end = int; };
class C { int* begin(); int *end(); }; // inaccessible
struct D { int* begin(int); int* end();};
struct E {};
template<class T> int* begin(T&) { return nullptr; }
template<class T> int* end(T&) { return nullptr; }
}
foo::A a; foo::B b; foo::C c; foo::D d; foo::E e;
/ magic_begin(a)
/ magic_begin(b)
/ magic_begin(c)
成为编译错误,并magic_begin(d)
返回magic_begin(e)
。
答案 0 :(得分:12)
以下SFINAE友好方法似乎可以按预期工作(参见下面的例外情况):
#include <type_traits>
namespace detail {
struct empty {};
template <typename T>
using base = std::conditional_t<std::is_class<T>{} && not std::is_final<T>{},
T, empty>;
struct P1 {typedef int begin, end;};
template <typename U>
struct TestMemType : base<U>, P1 {
template <typename T=TestMemType, typename=typename T::begin>
static std::true_type test_begin(int);
template <typename T=TestMemType, typename=typename T::end>
static std::true_type test_end(int);
static std::false_type test_begin(float), test_end(float);
};
template <typename T>
constexpr bool hasMember = !decltype(TestMemType<T>::test_begin(0)){}
|| !decltype(TestMemType<T>::test_end(0)){};
//! Step 1
template <typename T, std::size_t N>
constexpr auto begin(int, T(&a)[N]) {return a;}
template <typename T, std::size_t N>
constexpr auto end(int, T(&a)[N]) {return a+N;}
//! Step 2 - this overload is less specialized than the above.
template <typename T>
constexpr auto begin(int, T& a) -> decltype(a.begin()) {return a.begin();}
template <typename T>
constexpr auto end(int, T& a) -> decltype(a.end()) {return a.end();}
//! Step 3
namespace nested_detail {
void begin(), end();
template <typename T>
constexpr auto begin_(T& a) -> decltype(begin(a)) {return begin(a);}
template <typename T>
constexpr auto end_(T& a) -> decltype(end(a)) {return end(a);}
}
template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
constexpr auto begin(float, T& a) -> decltype(nested_detail::begin_(a))
{return nested_detail::begin_(a);}
template <typename T, typename=std::enable_if_t<not hasMember<std::decay_t<T>>>>
constexpr auto end(float, T& a) -> decltype(nested_detail::end_(a))
{return nested_detail::end_(a);}
}
template <typename T>
constexpr auto magic_begin(T& a) -> decltype(detail::begin(0, a))
{return detail::begin(0, a);}
template <typename T>
constexpr auto magic_end (T& a) -> decltype(detail::end (0, a))
{return detail:: end(0, a);}
Demo。请注意,GCC查找已被删除,因为它没有考虑typename T::begin
中TestMemType::test_end/begin
的非类型名称。可以找到解决方案草图here。
第2步中的检查要求类类型是可派生的,这意味着此方法无法正确使用final
类或联合 - 如果这些类具有名称为{{1}的无法访问的成员} / begin
。
答案 1 :(得分:1)
几乎。
如果有效,则执行#1;如果有效则执行#2;如果无效,则执行#3#3是非常基本的标签调度/执行操作。
对于#3:
创建一个其他地方使用的命名空间。将它嵌入另一个。
在外部,放置一个=delete
开始函数,它可以获取任何内容。
放入一个调用begin
的辅助函数。
这将找到adl开始,否则删除开始。
失败模式是命名空间可以在其他地方使用;没有办法阻止它。