Как проверить, есть ли у класса метод
От: ArtK  
Дата: 14.10.14 18:57
Оценка: 15 (2)
Привет.
На cppcon 2014 был доклад для про современное метопрограммирование шаблонов — слайды.
Там в частности рассказывалось как реализовать проверку существует ли для класса оператор копирующего присваивания. Суть метода в том, чтобы получить тип результата некоторого выражения (в данном случае оператора присваивания) через decltype, и инстанцировать этим типом шаблон. Если выражение корректно (то есть у нас есть оператор присваивания), то результатом параметризации будет один тип (например std::true_type), если нет, то другой тип (например std::false_type).

Пример кода из слайдов:
template< class T >
using copy_assign_t = decltype( declval<T&>( ) = declval< T const& >( ) );

template< class T >
struct is_copy_assignable {
private:
  template< class U, class = copy_assign_t<U> >
  static true_type try_assign( U&& );               // SFINAE may apply!
  static false_type try_assign( . . . );            // catch-all overload
public:
  using type = decltype( try_assign( declval<T>( ) ) );
};

Я попробовал написать общий класс, который возвращает true_type/false_type в зависимости от того, является ли любое переданное выражение корректным. Его можно использовать, чтобы реализовать свои проверки для типов, при этом не требуется писать много кода.
Например нужно проверить, есть ли в классе нестатическая функция get_value():
//Объявляем тип выражения
template <typename T>
using has_get_value_t = decltype(std::declval<T>().get_value());

template <typename T>
struct has_get_value : is_correct_expression_t<T, has_get_value_t> { };

int main() {
  std::cout << std::is_same<std::true_type, has_get_value<MyClass>::type>::value << std::endl;
}

Как это реализовано:
template <typename T, template<class> class E>
struct is_correct_expression_t {
private:
    template <typename U, typename = E<U>>
    static std::true_type try_evaluate(U&&);
    static std::false_type try_evaluate(...);
public:
    using type = decltype(try_evaluate(std::declval<T>()));
};

Для move/copy assign будет выглядеть так:
template <typename T>
using copy_assign_t = decltype(std::declval<T&>() = std::declval<const T&>());

template <typename T>
struct is_copy_assignable : is_correct_expression_t<T, copy_assign_t> { };

template <typename T>
using move_assign_t = decltype(std::declval<T&>() = std::declval<T&&>());

template <typename T>
struct is_move_assignable : is_correct_expression_t<T, move_assign_t> { };

Я новенький в этой чёрной магии, так что если будут замечания или предложения по улучшению буду рад выслушать.
Спасибо!
Re: Как проверить, есть ли у класса метод
От: Evgeny.Panasyuk Россия  
Дата: 14.10.14 19:15
Оценка: 6 (1)
по теме
Автор: Evgeny.Panasyuk
Дата: 24.07.13
Отредактировано 14.10.2014 19:19 Evgeny.Panasyuk . Предыдущая версия .
Re[2]: Как проверить, есть ли у класса метод
От: Evgeny.Panasyuk Россия  
Дата: 14.10.14 19:18
Оценка:
да, и кстати:
Boost: The Type Traits Introspection Library
Re: Как проверить, есть ли у класса метод
От: Evgeny.Panasyuk Россия  
Дата: 14.10.14 19:57
Оценка:
Здравствуйте, ArtK, Вы писали:

AK>Я новенький в этой чёрной магии, так что если будут замечания или предложения по улучшению буду рад выслушать.


AK>
AK>template <typename T, template<class> class E>
AK>


Тут хорошо бы поддерживать E с не фиксированным количеством шаблонных параметров. Например std::is_convertible имеет два параметра.

AK>
AK>template <typename T>
AK>struct is_move_assignable : is_correct_expression_t<T, move_assign_t> { };
AK>


Здесь по идее можно сразу using.

P.S. использовать постоянно declval не удобно и хотелось бы иметь макрос облегчающий этот момент. Что-то типа вот этого
Автор: Evgeny.Panasyuk
Дата: 24.07.13
.
Re: Как проверить, есть ли у класса метод
От: slava_phirsov Россия  
Дата: 15.10.14 09:58
Оценка:
Здравствуйте, ArtK, Вы писали:

Главный вопрос не "как", а "зачем". Собственно, он мучает меня с тех пор, когда я впервые увидел подобные трюки, ЕМНИП, у Джозаттиса. Все, конечно, фокусы, имеют право на жизнь, но — зачем?
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[2]: Как проверить, есть ли у класса метод
От: Evgeny.Panasyuk Россия  
Дата: 15.10.14 10:11
Оценка:
Здравствуйте, slava_phirsov, Вы писали:

_>Главный вопрос не "как", а "зачем". Собственно, он мучает меня с тех пор, когда я впервые увидел подобные трюки, ЕМНИП, у Джозаттиса. Все, конечно, фокусы, имеют право на жизнь, но — зачем?


Проверка соответствия концепции по синтаксису Зачем? Например для перегрузки, более внятных ошибок компиляции.
Для подобных вещей в язык уже больше десяти лет пытаются добавить концепции, в следующем месяце должны наконец проголосовать
(естественно концепции будут мощнее трюка с decltype — там должно быть автоматическое разбиение на атомы, автоматическая перегрузка, краткий синтаксис).
Re[3]: Как проверить, есть ли у класса метод
От: slava_phirsov Россия  
Дата: 15.10.14 10:26
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Проверка соответствия концепции по синтаксису Зачем? Например для перегрузки, более внятных ошибок компиляции.

EP>Для подобных вещей в язык уже больше десяти лет пытаются добавить концепции, в следующем месяце должны наконец проголосовать

Ну вот концепты, с простым синтаксисом — другой разговор, а вот подобная камасутра, ИМХО, с точки зрения проверки никакой пользы кроме вреда не принесёт.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[4]: Как проверить, есть ли у класса метод
От: Evgeny.Panasyuk Россия  
Дата: 15.10.14 10:36
Оценка:
Здравствуйте, slava_phirsov, Вы писали:

_>Ну вот концепты, с простым синтаксисом — другой разговор


Я отвечал на вопрос "зачем?". Простой синтаксис это не "зачем?", а "как?".

_>а вот подобная камасутра, ИМХО, с точки зрения проверки никакой пользы кроме вреда не принесёт.


Всего лишь инструмент, позволяющий делать определённые востребованные вещи
Конечно это как левой ногой правое ухо, но других средств на данный момент нет. И, например, в том же стандарте уже давно поселился std::enable_if.
Re[2]: Как проверить, есть ли у класса метод
От: ArtK  
Дата: 15.10.14 13:19
Оценка:
EP>Тут хорошо бы поддерживать E с не фиксированным количеством шаблонных параметров. Например std::is_convertible имеет два параметра.

Как это сделать через Variadic Template я не сообразил, но можно обойтись таким трюком:
template <typename T>
void try_convert(T);

template <typename TypePair>
using is_convertable_t = decltype(try_convert<typename TypePair::second_type>(std::declval<typename TypePair::first_type>()));

template <typename T1, typename T2>
struct is_convertable : is_correct_expression_t<std::pair<T1, T2>, is_convertable_t> {};

int main() {
  std::cout << std::is_same<std::true_type, is_convertable<int, float>::type>::value << "\n";
}
Re[2]: Как проверить, есть ли у класса метод
От: ArtK  
Дата: 16.10.14 11:51
Оценка:
Здравствуйте, slava_phirsov, Вы писали:

_>Главный вопрос не "как", а "зачем". Собственно, он мучает меня с тех пор, когда я впервые увидел подобные трюки, ЕМНИП, у Джозаттиса. Все, конечно, фокусы, имеют право на жизнь, но — зачем?


Вот прямо сегодня столкнулся с задачей.
Есть класс:
class string_istream {
public:
  explicit string_stream(const std::string &s_) : s(s_) {}
private:
  const std::string &s;
};

Т.к. он сохраняет ссылку на строку, строка не должна быть временным объектом, но временный объект передать можно т.к. конструктор принимает const std::string&.
Запретить такое поведение можно так:
class string_istream {
public:
  explicit string_istream(const std::string &s_) : s(s_) {}
  string_istream(const std::string &&) = delete;
private:
  const std::string &s;
};

У нас появляется некоторое требование к интерфейсу класса (нельзя конструировать от временного std::string), который имеет смысл проверять в unit-тестах. Эту проверку как раз можно делать с использованием описанных трюков.
template <typename T>
const T return_const_temp();

template <typename type_pair>
using is_constructable_from_temp_t = decltype(typename type_pair::first_type(return_const_temp<typename type_pair::second_type>()));

template <typename TClass, typename TParam>
struct is_constructable_from_temp : is_correct_expression_t<std::pair<TClass, TParam>, is_constructable_from_temp_t> {};

int main() {
  UNIT_TEST_ASSERT(std::is_same<std::false_type, is_constructable_from_temp<MyClass, std::string>::type>::value);
}
Re[3]: Как проверить, есть ли у класса метод
От: slava_phirsov Россия  
Дата: 16.10.14 12:58
Оценка:
Здравствуйте, ArtK, Вы писали:


AK>Вот прямо сегодня столкнулся с задачей.

...

Тест получается значительно сложнее кода, который он проверяет. А так быть не должно. Тест как правило всегда больше проверяемого кода по объему (иногда — на порядок), но он не должен быть сложнее, иначе это не тест, а Не говоря уже о том, что зачастую на тесты сажают людей не самой высокой квалификации. Так тоже быть не должно, кстати, но так бывает, причем нередко. И не стоит этим людям подкладывать такую заподлянку. ИМХО.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Re[4]: Как проверить, есть ли у класса метод
От: ArtK  
Дата: 16.10.14 13:29
Оценка:
Здравствуйте, slava_phirsov, Вы писали:

_>Тест получается значительно сложнее кода, который он проверяет. А так быть не должно. Тест как правило всегда больше проверяемого кода по объему (иногда — на порядок), но он не должен быть сложнее, иначе это не тест, а Не говоря уже о том, что зачастую на тесты сажают людей не самой высокой квалификации. Так тоже быть не должно, кстати, но так бывает, причем нередко. И не стоит этим людям подкладывать такую заподлянку. ИМХО.


Так я привёл код реализации. Естественно, чтобы это реализовать нужно знать C++ немного глубже. Но для пользователя код будет выглядеть так:
int main() {
  UNIT_TEST_ASSERT(std::is_same<std::false_type, is_constructable_from_temp<MyClass, std::string>::type>::value);
  UNIT_TEST_ASSERT(std::is_same<std::true_type, is_copyable<MyClass>::type>::value);
  UNIT_TEST_ASSERT(std::is_same<std::true_type, has_star_operator<MyClass>::type>::value);
  UNIT_TEST_ASSERT(std::is_same<std::true_type, has_binay_minus_operator<MyClass>::type>::value);
  UNIT_TEST_ASSERT(std::is_same<std::false_type, can_be_base_class<MyClass>::type>::value);
}
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.