вторник, 31 мая 2016 г.

Сложные type_trait'ы старые студии и свежие gcc

Продолжая баловаться со 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) {}};
};
ибо понять такой код неправильно много сложней чем понять правильно финальную версию рабочей реализации моего трейта.

Комментариев нет: