Информация об изменениях

Сообщение Re[7]: Философско-практические вопросы про метапрограммирова от 09.02.2023 16:00

Изменено 09.02.2023 20:00 rg45

Re[7]: Философско-практические вопросы про метапрограммирова
Здравствуйте, Максим, Вы писали:

М>А можете, если есть время, более подробно написать (может пару строчек кода) как такое делается? У меня пока только совсем простые вещи получаются, типа проверок есть ли определенный метод в классе или нет. А те же begin/end в set есть и просто так не отделить set от vector. Что-то типа std::is_set_v<T> вроде нет в библиотеке. Вероятнее всего, я пока просто не понимаю, что такое traits в плюсах . Спасибо за помощь!


Ну, примерно вот так. Нужно только понимать, что это эскиз, а не продакшн код, поэтому и воспринимать нужно с некоторой поправкой.

Хочется сразу сделать небольшое замечание по поводу traits-ов. В традиционном понимании traits-ы — это шаблонный клас со специализациями, содержащий в себе фиксированный набор свойств типов, которые используются в определенной обобщенной процедуре (или группе процедур). В этом примере использован немного другой подход. Traits-ы здесь представлены набором объявлений функций — именно объявлений, без определений. Эти объявления логически играют ту же самую роль — связывают между собой различные сущности времени компиляции — типы и константы. Но, по сравнению с классическим подходом, подход с фунциями имеет некоторые существенные преимущества: они допускают различную группировку по типам для разных свойств, а еще функции подчиняются правилам overload resolution и поиска по ADL. Трейтсы-функции объявляются в тех же пространствах имен, что и сами типы, прямо рядышком. Это здорово облегчает определение трейтсов и способствует комфортной расширяемости обобщенных процедур для пользовательских типов. Но есть и минус: трейтсы для стандартных типов и типов из сторонних библиотек должны быть видимы в точке использования (в метафункциях) и их приходится определять заранее. Но как по мне, это не большая плата за те преимущества, которые дает этот подход, а где-то это даже и логично.

http://coliru.stacked-crooked.com/a/c94ee80959031ffc

#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <utility>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Generic Library Area

template <typename, typename = void>
struct has_sort_member_function : std::false_type {};

template <typename T>
struct has_sort_member_function<T, std::void_t<decltype(std::declval<T&>().sort())>> : std::true_type {};

template <typename, typename = void>
struct is_std_sort_applicable : std::false_type{};

template <typename T>
struct is_std_sort_applicable<T, std::void_t<decltype(std::sort(
   std::begin(std::declval<T>()),
   std::end(std::declval<T>())
))>> : std::true_type {};

template <typename T>
auto assume_sorted_range(const T&) -> std::false_type;

// For standard containers, the traits must be defined beforehand
template <typename K, typename T, typename C, typename A>
auto assume_sorted_range(const std::map<K, T, C, A>&) -> std::true_type;

template <typename K, typename T, typename C, typename A>
auto assume_sorted_range(const std::multimap<K, T, C, A>&) -> std::true_type;

template <typename T, typename C, typename A>
auto assume_sorted_range(const std::set<T, C, A>&) -> std::true_type;

template <typename T, typename C, typename A>
auto assume_sorted_range(const std::multiset<T, C, A>&) -> std::true_type;

template <typename T>
using is_sorted_container = decltype(assume_sorted_range(std::declval<T>()));

template <typename T, std::enable_if_t<has_sort_member_function<T>::value, int> = 0>
void sort(T& t)
{
   t.sort();
}

template <typename T, std::enable_if_t<
   is_std_sort_applicable<T>::value &&
   !has_sort_member_function<T>::value &&
   !is_sorted_container<T>::value,
int> = 0>
void sort(T& t)
{
   std::sort(std::begin(t), std::end(t));
}

template <typename T, std::enable_if_t<
   is_sorted_container<T>::value &&
   !has_sort_member_function<T>::value,
int> = 0>
void sort(const T&)
{
   // Container is sorted already, so nothing to do.
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Custom Application Area

struct MyContainer
{
   void sort() { std::cout << "MyContainer::sort" << std::endl; }
};

struct MySortedContainer
{

};
auto assume_sorted_range(const MySortedContainer&) -> std::true_type;

int main()
{
   std::vector<int> vector { 3, 5, 2, 9, 1, 7, 4, 8, 0, 6 };
   sort(vector);

   std::set<int> set{ 3, 5, 2, 9, 1, 7, 4, 8, 0, 6 };
   sort(set);

   MyContainer my_container;
   sort(my_container);

   MySortedContainer my_sorted_container;
   sort(my_sorted_container); // The sort procedure has no effect
}
Re[7]: Философско-практические вопросы про метапрограммирова
Здравствуйте, Максим, Вы писали:

М>А можете, если есть время, более подробно написать (может пару строчек кода) как такое делается? У меня пока только совсем простые вещи получаются, типа проверок есть ли определенный метод в классе или нет. А те же begin/end в set есть и просто так не отделить set от vector. Что-то типа std::is_set_v<T> вроде нет в библиотеке. Вероятнее всего, я пока просто не понимаю, что такое traits в плюсах . Спасибо за помощь!


Ну, примерно вот так. Нужно только понимать, что это эскиз, а не продакшн код, поэтому и воспринимать нужно с некоторой поправкой.

Хочется сразу сделать небольшое замечание по поводу traits-ов. В традиционном понимании traits-ы — это шаблонный клас со специализациями, содержащий в себе фиксированный набор свойств типов, которые используются в определенной обобщенной процедуре (или группе процедур). В этом примере использован немного другой подход. Traits-ы здесь представлены набором объявлений функций — именно объявлений, без определений. Эти объявления логически играют ту же самую роль — связывают между собой различные сущности времени компиляции — типы и константы. Но, по сравнению с классическим подходом, подход с фунциями имеет некоторые существенные преимущества: они допускают различную группировку по типам для разных свойств, а еще функции подчиняются правилам overload resolution и поиска по ADL. Трейтсы-функции объявляются в тех же пространствах имен, что и сами типы, прямо рядышком. Это здорово облегчает определение трейтсов и способствует комфортной расширяемости обобщенных процедур для пользовательских типов. Но есть и минус: трейтсы для стандартных типов и типов из сторонних библиотек должны быть видимы в точке использования (в метафункциях) и их приходится определять заранее. Но как по мне, это не большая плата за те преимущества, которые дает этот подход, а где-то это даже и логично.

http://coliru.stacked-crooked.com/a/acaaa2be2cae47a7

#include <algorithm>
#include <iostream>
#include <map>
#include <set>
#include <vector>
#include <utility>

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Generic Library Area

template <typename, typename = void>
struct has_sort_member_function : std::false_type {};

template <typename T>
struct has_sort_member_function<T, std::void_t<decltype(std::declval<T&>().sort())>> : std::true_type {};

template <typename, typename = void>
struct is_std_sort_applicable : std::false_type{};

template <typename T>
struct is_std_sort_applicable<T, std::void_t<
   decltype(std::sort(std::begin(std::declval<T&>()), std::end(std::declval<T&>()))),
   decltype(*std::begin(std::declval<T&>()) = *std::begin(std::declval<T&>()))
>> : std::true_type {};

template <typename T>
auto assume_sorted_range(const T&) -> std::false_type;

// For standard containers, the traits must be defined beforehand
template <typename K, typename T, typename C, typename A>
auto assume_sorted_range(const std::map<K, T, C, A>&) -> std::true_type;

template <typename K, typename T, typename C, typename A>
auto assume_sorted_range(const std::multimap<K, T, C, A>&) -> std::true_type;

template <typename T, typename C, typename A>
auto assume_sorted_range(const std::set<T, C, A>&) -> std::true_type;

template <typename T, typename C, typename A>
auto assume_sorted_range(const std::multiset<T, C, A>&) -> std::true_type;

template <typename T>
using is_sorted_container = decltype(assume_sorted_range(std::declval<T>()));

template <typename T, std::enable_if_t<has_sort_member_function<T>::value, int> = 0>
void sort(T& t)
{
   t.sort();
}

template <typename T, std::enable_if_t<
   is_std_sort_applicable<T>::value &&
   !has_sort_member_function<T>::value &&
   !is_sorted_container<T>::value,
int> = 0>
void sort(T& t)
{
   std::sort(std::begin(t), std::end(t));
}

template <typename T, std::enable_if_t<
   is_sorted_container<T>::value &&
   !has_sort_member_function<T>::value,
int> = 0>
void sort(const T&)
{
   // Container is sorted already, so nothing to do.
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Custom Application Area

struct MyContainer
{
   void sort() { std::cout << "MyContainer::sort" << std::endl; }
};

struct MySortedContainer
{

};
auto assume_sorted_range(const MySortedContainer&) -> std::true_type;

int main()
{
   std::string string = "Hello World!";
   sort(string);
   std::cout << "<" << string << ">" << std::endl;

   int array[] { 3, 5, 2, 9, 1, 7, 4, 8, 0, 6 };
   sort(array);

   std::vector<int> vector { 3, 5, 2, 9, 1, 7, 4, 8, 0, 6 };
   sort(vector);

   std::set<int> set { 3, 5, 2, 9, 1, 7, 4, 8, 0, 6 };
   sort(set); // no effect

   MyContainer my_container;
   sort(my_container);

   MySortedContainer my_sorted_container;
   sort(my_sorted_container); // no effect
}