Продолжая баловаться со static_reflection захотел уметь проверять возможно ли проитерироваться по заданному типу используя C++11 range-for. Попытавшись написать вот так:
template<typename T, typename = void_t<>>
struct is_iterable: false_type {};
template<typename T>
struct is_iterable<
T, void_t<
decltype(for (const auto& i: declval<const T&>()) {})
>
>: true_type {};
я разумеется сразу же понял, что Expression SFINAE неприменим для проверки правильности того, что является Statement с точки зрения языка C++. Опечалившись этим фактом я решил воспользоваться наличием под рукой GCC 6.1 известного тем, что это единственный в мире компилятор с поддержкой концептов. И так родился следующий код:
template<typename T>
concept bool Iterable = requires(const T& t) {
{for (const auto& item: t) {}};
};
который был безбожно забрит компилятором. Чтение спецификации Concepts Light TS подтвердило правоту компилятора, но пошатнуло мою веру в разумное, доброе, вечное. Оказывается requires позволяет проверять возможность использования своих операндов в выражениях (expressions), а в операциях (statement) не позволяет.
Всё ещё опечаленный сим фактом я вернулся к имеющемуся у меня в боевых условиях GCC 4.9 написал работающую проверку использую многа ненужных букаф:
using std::begin;
using std::end;
template<typename T, typename = void_t<>>
struct is_iterable: false_type {};
template<typename T>
struct is_iterable<
T,
void_t<
decltype(begin(declval<const T&>())),
decltype(end(declval<const T&>())),
decltype(begin(declval<const T&>()) != end(declval<const T&>())),
decltype(++begin(declval<const T&>())),
decltype(*begin(declval<const T&>()))
>
>: true_type {};
И пошёл компилировать всё это дело в стуиях. 15 это всё съела, чем меня несказанно порадовала. Это впервые на моей памяти, когда компилятор от мелкомягких просто и спокойно скомпилировал нетривиальный шаблон отлаженный на GCC с CLANG'ом.
Но из за WinPhone8 я был обречён на подлый удар в спину от студии 13ой. Гугл, stack-overflow и два дня экспериментов позволили найти выход. Который уже работает везде:
using std::begin;
using std::end;
template<typename T>
auto test_begin(const T& t) -> decltype(begin(t));
auto test_begin(...) -> false_type;
template<typename T>
auto test_end(const T& t) -> decltype(end(t));
auto test_end(...) -> false_type;
template<typename T>
auto test_it_noteq_end(const T& t) -> decltype(begin(t) != end(t));
auto test_it_noteq_end(...) -> false_type;
template<typename T>
auto test_it_increment(const T& t) -> decltype(++begin(t));
auto test_it_increment(...) -> false_type;
template<typename T>
auto test_it_deref(const T& t) -> decltype(*begin(t));
auto test_it_deref(...) -> false_type;
template<typename T>
using is_iterable = typename conditional<
(
!is_same<decltype(test_begin(declval<const T&>())), false_type>::value &&
!is_same<decltype(test_end(declval<const T&>())), false_type>::value &&
!is_same<decltype(test_it_noteq_end(declval<const T&>())), false_type>::value &&
!is_same<decltype(test_it_increment(declval<const T&>())), false_type>::value &&
!is_same<decltype(test_it_deref(declval<const T&>())), false_type>::value
),
true_type,
false_type
>::type;
И всё же я безумно хочу, чтобы можно было написать вот так:
template<typename T>
concept bool Iterable = requires(const T& t) {
{for (const auto& item: t) {}};
};
ибо понять такой код неправильно много сложней чем понять правильно финальную версию рабочей реализации моего трейта.