Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.50/6: Рейтинг темы: голосов - 6, средняя оценка - 4.50
Эксперт С++
8739 / 4317 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
1

[Дизайн и эволюция] Дискриминация шаблона на примере макроса OUT_TO_STREAM

22.02.2016, 11:06. Показов 1168. Ответов 9
Метки нет (Все метки)

Author24 — интернет-сервис помощи студентам
рублика:
дизайн и эволюция

название:
дискриминация шаблона на примере макроса OUT_TO_STREAM

категории:
с++,
ненормальное программирование,
для настоящих ценителей.

Часть 0. вступление.

всем привет.

дискриминатор - это конструкция,
которая выносит мозг и компиляторам, и людям на ровном месте усложняет код.

существует множество разновидностей дискриминаторов.
объединяет их то, что они используют неочевидные хаки трюки
где то на пределах возможностей языка.

например, можно поиметь эффект "повторного инстанцирования" шаблона.
при этом, предыдущий инстанс - дискриминируется,
как бы списывается на берег,
выбрасывается нафиг,
сливается в клоаку,
девочки его больше не любят.

Кликните здесь для просмотра всего текста

изначально, где то на просторах интернетов,
я нашел пример-иллюстрацию шаблона,
у которого один из параметров так и назывался: discriminator
в качестве значения дискриминатора,
через макрос подставлялся __COUNTER__
что приводило к тому, что шаблон инстанцировался каждый раз заново.

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

помимо __COUNTER__ есть и другие способы добиться аналогичного эффекта.
один из них, весьма экзотичный, имеет особенный эффект:
реагирует на неполные типы.

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

Кликните здесь для просмотра всего текста
изначально дискриминатор потребовался для реализации
шаблона is_complete<type>::value
который вернет true, если тип полный.

пример:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
 
int main(){
 
    struct foo; 
 
    // первое инстанцирование шаблона
    std:: cout <<"is complete? " << is_complete(foo); // false
 
    struct foo{};
 
    // повторное инстанцирование шаблона
    std:: cout <<"is complete? " << is_complete(foo); // true
}
в рамках с++03 общего решения этой задачи не существует.
мне удалось добиться только 3х ответов: да, нет, не знаю.

в рамках с++11 использовал необычный трюк:

C++
1
2
3
4
5
6
constexpr int flag();
constexpr int a = noexcept(flag()); // false
constexpr int flag() { return 0; };
constexpr int b = noexcept(flag()); // true
 
int main () {  static_assert (a != b, "fail"); }
здесь компалтайм конструкция:
C++
1
noexcept(flag());
при повторном использовании возвращает разные результаты,
что позволяет, о боже! наделить компалтайм вычисление переменным состоянием.

Первое от чего отталкивается подход - что constexpr-функция может быть декларирована,
но пока не реализовано её тело, то вызвать её нельзя.
Вокруг этого уже можно построить SFINAE,
а можно воспользоваться тем что стандартный std::noexcept
уже будет работать так же - вернет в компил-тайм false
если функция объявлена но еще не определена и true наоборот.
(Ц)=A=L=X=
http://b.atch.se/posts/non-con... pressions/

материал по is_complete уже почти готов,
так что скоро я его опубликую.



другая - взять под контроль конкурс претендентов на инстанцирование среди перегрузок.
именно этой технике и посвящается данный материал.

Часть 1. макрос OUT_TO_STREAM

это - наш подопытный кролик, на примере которого
я решил проиллюстрировать технику в действии.

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

встречайте:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#ifndef OUT_TO_STREAM
    #define OUT_TO_STREAM(type_)  
        template<class T>friend   
        ::std::basic_ostream<T>&  
        operator<<(::std::basic_ostream<T>& os, const type_& obj )
#endif
 
#ifndef FROM_STREAM
#define FROM_STREAM(type_)  
        template<class T>friend   
        ::std::basic_istream<T>&  
        operator>>(::std::basic_istream<T>& is, type_& obj )
#endif
 
#include <iostream>
 
struct sample
{
    OUT_TO_STREAM(sample)
    {
        return os << "name: " << obj.mName 
                 <<" : age: " << obj.mAge <<'n';
    }
    
    FROM_STREAM(sample)
    {
        return is >> obj.mName >> obj.mAge;
    }
 
    std::string mName;
    size_t      mAge;
};
 
int main()
{
    sample s;
    
    std::cin >> s;
    std::cout <<"content: "<< s<<std::endl;
}
макросы OUT_TO_STREAM и FROM_STREAM служат для быстрого изготовления
дружественных функций ввода/вывода объекта в stl-совместимые потоки

на них налагаются требования:
1. если вывод в поток не используется,
то подключать #include <iostream> не обязательно.

2. универсальность. можно работать не только с std::iostream,
но и слюбыми stl-совместимыми потоками.

однако, представленная версия OUT_TO_STREAM обладает серьёзным недостатком,
которая ломает весь дизайн:

C++
1
2
3
4
OUT_TO_STREAM(sample) {
    // здесь мы гвоздями прибились к ansi-char
    return os << "name: " << obj.mName <<" : age: " << obj.mAge <<'n';
}
или:
C++
1
2
3
4
5
OUT_TO_STREAM(sample) {
    // здесь мы гвоздями прибились к unicode
    return os << L"name: " << obj.mName << L" : age: " << obj.mAge <<'n';
    // obj.mName - std::wstring
}
а что, если у нас - вариативный тип данных,
и нужно поддерживать оби версии?
и вот здесь дизайн ломается, и начинаются приседания с бубнами.

а хочется простоты.
хочется простого дизайна:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct sample
{
    OUT_TO_STREAM(sample) {
        return os << "общий случай: " << obj.mAge<<'n';
    }
    OUT_TO_STREAM(sample, char) {
        return os << "ansi-версия: " << obj.mName <<'n';
    }
    OUT_TO_STREAM(sample, wchar_t) {
        return os << L"unicode-версия: " << to_wstring(obj.mName) << L'n';
    }
 
    std::string mName;
    size_t      mAge;
};
обратите внимание, тут даже легаси не нарушается.
просто в любой момент можно добавить специализацию.

Часть 3. Дискриминация шаблона.
представленный выше дизайн весьма удобен в использовании.
но для его реализации нужно решить сразу две инженерные задачи.

первая связанна с тем, что макрос для общего случая,
и макрос для специализации должны раскрываться в принципиально
различный текст кода.

что бы такое провернуть,
потребуется реализовать перегрузку макросов
под разное количество аргументов.

как это сделать я расписывал раннее:
https://www.cyberforum.ru/cpp-... ost8733668


вторая проблема заключается в том,
что по задумке получается, что если специализация отсутствует,
должна запуститься общая версия.
а это значит, что параметры специализаций априори подходят под параметры общей версии.
а поскольку перегрузки участвуют в конкурсе на общих основаниях,
то получаем неоднозначность выбора:
компилятор не сможет понять, какую нужно выбрать версию:
общую, или специализацию.

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

и вот здесь проблема:
вы нигде не найдете точных правил
по которым определяется приоритет выбора претендента.

стандарт про это говорит крайне мало:
"выбрана должна быть наиболее подходящая версия"

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

то бишь, в первую очередь компиляторы рассматривают нешаблонные версии
где вообще не нужно ломать голову ничего инстанцировать.
затем рассматривают шаблоны попроще, затем посложнее, и тд.

если на каком ни будь этапе аргументы шаблона впишутся без необходимости каста,
то все, конкурс заканчивается.
другие шаблоны компилятор не рассматривает,
а значит и не инстанцирует.

именно на этой особенности базируется данный дискриминатор:
мы исскуственно усложняем общую версию шаблона так,
что бы в итоге конструкция оставалась эквивалентна более простой первичной версии.
но при этом, из-за переусложнения, компиляторы начнут плеваться, и воротить носы
рассматривать его только после более простых специализаций.

итак, проиллюстрирую решение на практике:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// ===================================================================================
// ===================================================================================
// ===================================================================================
 
    // workaround.h
 
    // --- поддержка перегрузки макросов до 7 аргументов включительно
    #define dFUNC_CHOOSER_7(_f1, _f2, _f3, _f4, _f5, _f6, _f7, N, ... ) N
 
    #define dFUNC_RECOMPOSER(argsWithParentheses)\
        dFUNC_CHOOSER_7 argsWithParentheses
 
    #define dMACRO_CHOOSER(target_, ...)\
        dCHOOSE_FROM_ARG_COUNT(target_, target_##_NO_ARG_EXPANDER __VA_ARGS__ ())
 
    #define dCHOOSE_FROM_ARG_COUNT(arg_, ...) \
        dFUNC_RECOMPOSER((__VA_ARGS__, arg_##_7, arg_##_6, arg_##_5, \
            arg_##_4, arg_##_3, arg_##_2, arg_##_1, ))
 
// ===================================================================================
// ===================================================================================
// ===================================================================================
 
    // OUT_TO_STREAM
    
    #define dIS__(char_)\
        ::std::is_same<Ch, char_>::value
 
    #define dSTREAM__\
        ::std::basic_ostream<Ch>
 
    #define dDISCRIMINATOR__(type_)\
        class O = type_,           \
        typename ::std::enable_if< ::std::is_same<O, type_>::value >::type* = nullptr
 
    // --- специализация для указанного типа символа
    #define dOUT_TO_STREAM_2(type_, char_)  \
        template<class Ch>friend            \
        typename ::std::enable_if< dIS__(char_), dSTREAM__ >::type&  \
        operator<<(dSTREAM__& os, const type_& obj )
 
    // --- общий случай
    #define dOUT_TO_STREAM_1(type_) \
        template<class Ch, dDISCRIMINATOR__(type_) > friend \
        dSTREAM__&  \
        operator<<(dSTREAM__& os, const O& obj)
 
    #define dOUT_TO_STREAM_0() error
 
    #define dOUT_TO_STREAM_NO_ARG_EXPANDER() \
        ,,,,,,,dOUT_TO_STREAM_0
 
    // --- пользовательский макрос
    #define OUT_TO_STREAM(...)\
        dMACRO_CHOOSER( dOUT_TO_STREAM, __VA_ARGS__)(__VA_ARGS__)
 
// ===================================================================================
// ===================================================================================
// ===================================================================================
    
#include <iostream>
#include <string>
 
struct sample
{
    // для общего случая
    OUT_TO_STREAM(sample)
    {
        //когда os умеет кушать все подряд
        return os << obj.age;
    } 
    
    // ansi
    OUT_TO_STREAM(sample, char)
    {
        return os << "ansi: " << "name: " << obj.name <<" : age: " << obj.age;
    }
    
    // unicode
    OUT_TO_STREAM(sample, wchar_t)
    {
        return os << L"unicode: name: " << obj.wname <<L" : age: " << obj.age;
    }
    
    std::string   name = "ololo";
    std::wstring wname = L"ololo";
    size_t         age = 18;
};
 
 
 
int main()
{
    std::cout << "Hello, world!\n";
    sample s;
    
    std::cout  << s << std::endl;
    std::wcout << s << std::endl;
}
обратите внимание, шаблон специализации:
C++
1
template<class Ch>friend
и общий:
C++
1
template<class Ch, dDISCRIMINATOR__(type_) > friend \
где дискриминатор раскрывается в два дополнительных параметра:

C++
1
2
3
4
5
6
7
// после раскрытия макроса это будет выглядеть так:
template<class Ch, 
    class O = type_, 
    typename ::std::enable_if< 
        ::std::is_same<O, type_>::value 
    >::type* = nullptr
>
первый шаблон очень простой: всего один обычный параметр.

второй шаблон замороченный.
у него целых три.
из которых 3й зависит от 2го, а 2й выводится по умолчанию.
это дает такую нагрузку на парсинг,
что компилятор вижал студии, жосиси, и шланг
сразу же сблевали понизили приоритет общей версии
ниже плинтуса предпочтя более простую специализацию.

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

и таким образом становится возможным контролировать приоритеты
любых шаблонно-перегрузок,

на этом все.

всем пока.
6
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
22.02.2016, 11:06
Ответы с готовыми решениями:

Дизайн и эволюция: перегрузка макросов
Часть 0. Вместо предисловия. всем привет. недавно, для одной из моих задач, мне...

[дизайн и эволюция] провалы в variadic конструкторы
всем привет. уже несколько человек обращались ко мне по почте, с просьбой помочь разобраться с...

Прикол: Эволюция программиста на примере "Hello world"
Эволюция программиста 1. Старший курс школы. 10 PRINT 'HELLO WORLD' 20 END 2....

Установить иконки как в примере шаблона на главной
Здравствуйте. Помогите пожалуйста. Есть платный шаблон http://thevoux.fuelthemes.net/madison...

9
GbaLog-
25.04.2017, 06:27
  #2

Не по теме:

Цитата Сообщение от hoggy Посмотреть сообщение
материал по is_complete уже почти готов,
так что скоро я его опубликую.
тема начала 2016 года, а где же материал? :)

0
Неэпический
17870 / 10635 / 2054
Регистрация: 27.09.2012
Сообщений: 26,737
Записей в блоге: 1
25.04.2017, 08:32 3
Цитата Сообщение от GbaLog- Посмотреть сообщение
тема начала 2016 года, а где же материал?
is_complete не лыком шит.
Попробуйте написать для покрытия 100% случаев.
0
Любитель чаепитий
3742 / 1798 / 566
Регистрация: 24.08.2014
Сообщений: 6,016
Записей в блоге: 1
25.04.2017, 10:27 4
Цитата Сообщение от Croessmah Посмотреть сообщение
is_complete не лыком шит.
Попробуйте написать для покрытия 100% случаев.
да если бы я хоть представлял, как оно должно работать.
но вообще погуглил на эту тему и нашёл кое-каких котов, но не знаю, все ли случаи они покрывают.
пример:
C++
1
2
3
4
5
6
7
8
9
10
template <typename T>
struct is_complete_helper {
    template <typename U>
    static auto test(U*)  -> std::integral_constant<bool, sizeof(U) == sizeof(U)>;
    static auto test(...) -> std::false_type;
    using type = decltype(test((T*)0));
};
 
template <typename T>
struct is_complete : is_complete_helper<T>::type {};
какие случаи этот кот покроет, а какие нет?
0
Форумчанин
Эксперт CЭксперт С++
8215 / 5045 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 15:34 5
Больше всего мне понравилась подача материала
Даже с учётом того, что на практике мне такое никогда не требовалось, прочитал с удовольствием.
0
Неэпический
17870 / 10635 / 2054
Регистрация: 27.09.2012
Сообщений: 26,737
Записей в блоге: 1
25.04.2017, 15:58 6
Ответ от сопредседателя РГ 21 о введении std::is_complete:
На подобную тему был комментарий от России к C++17. В комментарии было требование, чтобы все type_triat проверяли тип на "завершённость" (если обратное не оговорено заранее:

"Failed prerequirement for the type trait must result in ill-formed program. Otherwise hard detectable errors will happen"

Это будет исправлено в ближайшее время, и почти все type_trait будут внутри иметь static_assert(complete). Можно будет ассертить на полноту например вот так:

C++
1
2
3
4
template<class T> 
constexpr void assert_complete() {
   (void)std::is_pod_v<T>;
}

Сделать именно type_trait std::is_complete_v<T> a не assert - невозможно, т.к. такой type_trait будет нарушать ODR и будет "запоминать" первый результат применения для типа T и всегда выдавать его.
0
Форумчанин
Эксперт CЭксперт С++
8215 / 5045 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 16:05 7
Цитата Сообщение от Croessmah Посмотреть сообщение
был комментарий от России к C++17
Неужто hoggy на https://stdcpp.ru proposal протащил?
0
Неэпический
17870 / 10635 / 2054
Регистрация: 27.09.2012
Сообщений: 26,737
Записей в блоге: 1
25.04.2017, 16:05 8
MrGluck, я запостил.
0
Форумчанин
Эксперт CЭксперт С++
8215 / 5045 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 16:22 9
Цитата Сообщение от Croessmah Посмотреть сообщение
MrGluck, я запостил.
Значит в РГ это посчитали действительно чем-то важным. Как они говорили на последней встрече, комментарий от страны - максимальная мера, которую они могут сделать. Потому что пока на этот комментарий не дадут вердикт, коммитет не может двигаться дальше, а они это не любят.

P.S. надо было щёлкнуть на porposal и ник посмотреть
0
Неэпический
17870 / 10635 / 2054
Регистрация: 27.09.2012
Сообщений: 26,737
Записей в блоге: 1
25.04.2017, 16:26 10
MrGluck, я конкретно про is_complete запостил,
а кто предложение вносил я не знаю.
0
25.04.2017, 16:26
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
25.04.2017, 16:26
Помогаю со студенческими работами здесь

Дизайн шаблона на дивах, съезжает футер
Не получается сделать футер наиже всех элементов на странице. Если контента мало, то он наползает...

Задать странице шаблона уникальный дизайн
Здравствуйте, уважаемые форумчане! Подскажите, пожалуйста, как можно задать уникальный дизайн...

Перестал обновляться дизайн агентов из шаблона
Здравия всем! Возникла странная проблема — при обновлении дизайна базы из шаблона не обновляются...

кто знает как выдрать дизайн шаблона на WordPress?
У меня есть шаблон на WordPress он мне очень нравиться, но ни никаком движке сайта не хочу. Может,...

Однократный запуск макроса при создании документа из шаблона
Приветствую! Недавно я задался такой задачей. Нужно сделать шаблон с макросом. Суть такова - когда...

Создание макроса для открытия существующего шаблона Word из Exel
Добрый день! Не знаю точно к какой теме относится этот вопрос, но главная проблема у меня...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru