среда, 9 марта 2016 г.

Концепты для бедных

Когда смотришь на то, что предполагается в Concepts Light в C++, то аппетитно облизываешься на возможность перегрузки шаблонных функций по именам концептов как по типам. Например, функция записи в данных в поток могла бы иметь следующие перегрузки:

void write(std::ostream& out, const DirectWritable& val);
void write(std::ostream& out, const Reflectable& val);

для типов которые можно записать используюя operator<< (DirectWritable) и типов которые разметили используюя технику описанную в предыдущем моём посте (Reflectable). Можно ли получить такой же функционал без компилятора который бы поддерживал Concepts Light (на данный момент оный TS поддержан только в ветке по разработке gcc 6 релиз из которой ожидается вроде этой весной)?

Ответ, да можно! Но только отчасти. Сегодня для таких перегрузок используется SFINAE и выглядит это страшно:

template<typename T>
void write(
  std::ostream& out,
  const typename std::enable_if<
    is_direct_writable<T>::value,
  T>::type& val
);

Но если приглядеться внимательно, то можно заметить, что второй параметр очень похож на то, что хочется получить. А начит можно записать его вот так:

template<typename T>
using DirectWritable = typename std::enable_if<
  is_direct_writable<T>::value,
  T
>::type;

template<typename T>
void write(std::ostream& out, const DirectWritable<T>& val);

Правда тут вылазит то самое слово "отчасти", что я употребил чуть выше. Дело в том, что такой подход убивает самую важную фичу шаблонных функций: вывод параметров шаблона из типов аргументов. И, несомненно, упущенным оказывается важный момент идеи концептов: красивые и понятные сообщения об ошибках. Чтобы устранить эти недостатки, надо выплонить "закат солнца вручную", а имнно унести такие перегрузки в условно приватное пространство имён detail (такое имя используется в реализации стандартной библиотеки поставляемой с gcc для сокрытия деталей реализации) и завести шаблонную функу, которая будет выводить параметры и генерировать красивые сообщения об ошибках:

template<typename T>
void write(std::ostream& out, const T& val) {
  static_assert(
    is_direct_writable<T>::value || is_reflectable<T>::value,
    "val argument must satisfy one of the following concepts: DirectWritable, Reflectable"
  );
  detail::write<T>(out, val);
}

На самом деле преимуществ от такого подхода к сокрытию SFINAE достаточно, чтобы не лениться и писать подобные мусорные функции-обёртки при работе с обобщённым кодом сегодя в ожидании того светлого завтра, когда у нас будут Concepts Lite.

Продолжение следует...

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