Ответ 1
Следующий подход, благоприятный для 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);}
Демо. Обратите внимание, что поиск GCC нарушен, поскольку он не учитывает имена не-типа для typename T::begin
в TestMemType::test_end/begin
. Обходной эскиз можно найти здесь.
Для проверки на шаге 2 требуется, чтобы тип класса был выводимым, что означает, что этот метод не работает с классами или объединениями final
, если они имеют недопустимый член с именем begin
/end
.