Сообщение Re[7]: Философско-практические вопросы про метапрограммирова от 09.02.2023 16:00
Изменено 09.02.2023 16:06 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/82b8b56a11d57c63
М>А можете, если есть время, более подробно написать (может пару строчек кода) как такое делается? У меня пока только совсем простые вещи получаются, типа проверок есть ли определенный метод в классе или нет. А те же begin/end в set есть и просто так не отделить set от vector. Что-то типа std::is_set_v<T> вроде нет в библиотеке. Вероятнее всего, я пока просто не понимаю, что такое traits в плюсах . Спасибо за помощь!
Ну, примерно вот так. Нужно только понимать, что это эскиз, а не продакшн код, поэтому и воспринимать нужно с некоторой поправкой.
Хочется сразу сделать небольшое замечание по поводу traits-ов. В традиционном понимании traits-ы — это шаблонный клас со специализациями, содержащий в себе фиксированный набор свойств типов, которые используются в определенной обобщенной процедуре (или группе процедур). В этом примере использован немного другой подход. Traits-ы здесь представлены набором объявлений функций — именно объявлений, без определений. Эти объявления логически играют ту же самую роль — связывают между собой различные сущности времени компиляции — типы и константы. Но, по сравнению с классическим подходом, подход с фунциями имеет некоторые существенные преимущества: они допускают различную группировку по типам для разных свойств, а еще функции подчиняются правилам overload resolution и поиска по ADL. Это здорово облегчает определение трейтсов и способствует комфортной расширяемости обобщенных процедур для пользовательских типов. Но есть и минус: трейтсы для стандартных типов и типов из сторонних библиотек должны быть видимы в точке использования (в метафункциях) и их приходится определять заранее. Но как по мне, это не большая плата за те преимущества, которые дает этот подход, а где-то это даже и логично.
http://coliru.stacked-crooked.com/a/82b8b56a11d57c63
#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 consider_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 consider_sorted_range(const std::map<K, T, C, A>&) -> std::true_type;
template <typename K, typename T, typename C, typename A>
auto consider_sorted_range(const std::multimap<K, T, C, A>&) -> std::true_type;
template <typename T, typename C, typename A>
auto consider_sorted_range(const std::set<T, C, A>&) -> std::true_type;
template <typename T, typename C, typename A>
auto consider_sorted_range(const std::multiset<T, C, A>&) -> std::true_type;
template <typename T>
using is_sorted_container = decltype(consider_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 consider_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/82b8b56a11d57c63
М>А можете, если есть время, более подробно написать (может пару строчек кода) как такое делается? У меня пока только совсем простые вещи получаются, типа проверок есть ли определенный метод в классе или нет. А те же begin/end в set есть и просто так не отделить set от vector. Что-то типа std::is_set_v<T> вроде нет в библиотеке. Вероятнее всего, я пока просто не понимаю, что такое traits в плюсах . Спасибо за помощь!
Ну, примерно вот так. Нужно только понимать, что это эскиз, а не продакшн код, поэтому и воспринимать нужно с некоторой поправкой.
Хочется сразу сделать небольшое замечание по поводу traits-ов. В традиционном понимании traits-ы — это шаблонный клас со специализациями, содержащий в себе фиксированный набор свойств типов, которые используются в определенной обобщенной процедуре (или группе процедур). В этом примере использован немного другой подход. Traits-ы здесь представлены набором объявлений функций — именно объявлений, без определений. Эти объявления логически играют ту же самую роль — связывают между собой различные сущности времени компиляции — типы и константы. Но, по сравнению с классическим подходом, подход с фунциями имеет некоторые существенные преимущества: они допускают различную группировку по типам для разных свойств, а еще функции подчиняются правилам overload resolution и поиска по ADL. Трейтсы-функции объявляются в тех же пространствах имен, что и сами типы, прямо рядышком. Это здорово облегчает определение трейтсов и способствует комфортной расширяемости обобщенных процедур для пользовательских типов. Но есть и минус: трейтсы для стандартных типов и типов из сторонних библиотек должны быть видимы в точке использования (в метафункциях) и их приходится определять заранее. Но как по мне, это не большая плата за те преимущества, которые дает этот подход, а где-то это даже и логично.
http://coliru.stacked-crooked.com/a/82b8b56a11d57c63
#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 consider_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 consider_sorted_range(const std::map<K, T, C, A>&) -> std::true_type;
template <typename K, typename T, typename C, typename A>
auto consider_sorted_range(const std::multimap<K, T, C, A>&) -> std::true_type;
template <typename T, typename C, typename A>
auto consider_sorted_range(const std::set<T, C, A>&) -> std::true_type;
template <typename T, typename C, typename A>
auto consider_sorted_range(const std::multiset<T, C, A>&) -> std::true_type;
template <typename T>
using is_sorted_container = decltype(consider_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 consider_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
}