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

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

22.02.2016, 11:06. Показов 1458. Ответов 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
Ответ Создать тему
Новые блоги и статьи
Советы по крайней бережливости. Внимание, это ОЧЕНЬ длинный пост.
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? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru