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

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

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

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

название:
дискриминация шаблона на примере макроса 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
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
22.02.2016, 11:06
Ответы с готовыми решениями:

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

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

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

9
25.04.2017, 06:27

Не по теме:

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

0
Неэпический
 Аватар для Croessmah
18144 / 10728 / 2066
Регистрация: 27.09.2012
Сообщений: 27,026
Записей в блоге: 1
25.04.2017, 08:32
Цитата Сообщение от GbaLog- Посмотреть сообщение
тема начала 2016 года, а где же материал?
is_complete не лыком шит.
Попробуйте написать для покрытия 100% случаев.
0
Любитель чаепитий
 Аватар для GbaLog-
3745 / 1801 / 566
Регистрация: 24.08.2014
Сообщений: 6,020
Записей в блоге: 1
25.04.2017, 10:27
Цитата Сообщение от 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Эксперт С++
 Аватар для MrGluck
8216 / 5047 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 15:34
Больше всего мне понравилась подача материала
Даже с учётом того, что на практике мне такое никогда не требовалось, прочитал с удовольствием.
0
Неэпический
 Аватар для Croessmah
18144 / 10728 / 2066
Регистрация: 27.09.2012
Сообщений: 27,026
Записей в блоге: 1
25.04.2017, 15:58
Ответ от сопредседателя РГ 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Эксперт С++
 Аватар для MrGluck
8216 / 5047 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 16:05
Цитата Сообщение от Croessmah Посмотреть сообщение
был комментарий от России к C++17
Неужто hoggy на https://stdcpp.ru proposal протащил?
0
Неэпический
 Аватар для Croessmah
18144 / 10728 / 2066
Регистрация: 27.09.2012
Сообщений: 27,026
Записей в блоге: 1
25.04.2017, 16:05
MrGluck, я запостил.
0
Форумчанин
Эксперт CЭксперт С++
 Аватар для MrGluck
8216 / 5047 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
25.04.2017, 16:22
Цитата Сообщение от Croessmah Посмотреть сообщение
MrGluck, я запостил.
Значит в РГ это посчитали действительно чем-то важным. Как они говорили на последней встрече, комментарий от страны - максимальная мера, которую они могут сделать. Потому что пока на этот комментарий не дадут вердикт, коммитет не может двигаться дальше, а они это не любят.

P.S. надо было щёлкнуть на porposal и ник посмотреть
0
Неэпический
 Аватар для Croessmah
18144 / 10728 / 2066
Регистрация: 27.09.2012
Сообщений: 27,026
Записей в блоге: 1
25.04.2017, 16:26
MrGluck, я конкретно про is_complete запостил,
а кто предложение вносил я не знаю.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
25.04.2017, 16:26
Помогаю со студенческими работами здесь

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

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

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

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

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


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

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
Новые блоги и статьи
Модель микоризы: классовый агентный подход 3
anaschu 06.01.2026
aa0a7f55b50dd51c5ec569d2d10c54f6/ O1rJuneU_ls https:/ / vkvideo. ru/ video-115721503_456239114
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR
ФедосеевПавел 06.01.2026
Owen Logic: О недопустимости использования связки «аналоговый ПИД» + RegKZR ВВЕДЕНИЕ Введу сокращения: аналоговый ПИД — ПИД регулятор с управляющим выходом в виде числа в диапазоне от 0% до. . .
Модель микоризы: классовый агентный подход 2
anaschu 06.01.2026
репозиторий https:/ / github. com/ shumilovas/ fungi ветка по-частям. коммит Create переделка под биомассу. txt вход sc, но sm считается внутри мицелия. кстати, обьем тоже должен там считаться. . . .
Расчёт токов в цепи постоянного тока
igorrr37 05.01.2026
/ * Дана цепь постоянного тока с сопротивлениями и напряжениями. Надо найти токи в ветвях. Программа составляет систему уравнений по 1 и 2 законам Кирхгофа и решает её. Последовательность действий:. . .
Новый CodeBlocs. Версия 25.03
palva 04.01.2026
Оказывается, недавно вышла новая версия CodeBlocks за номером 25. 03. Когда-то давно я возился с только что вышедшей тогда версией 20. 03. С тех пор я давно снёс всё с компьютера и забыл. Теперь. . .
Модель микоризы: классовый агентный подход
anaschu 02.01.2026
Раньше это было два гриба и бактерия. Теперь три гриба, растение. И на уровне агентов добавится между грибами или бактериями взаимодействий. До того я пробовал подход через многомерные массивы,. . .
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
Programma_Boinc 28.12.2025
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост. Налог на собак: https:/ / **********/ gallery/ V06K53e Финансовый отчет в Excel: https:/ / **********/ gallery/ bKBkQFf Пост отсюда. . .
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru