Форум программистов, компьютерный форум, киберфорум
Наши страницы

[дизайн и эволюция] провалы в variadic конструкторы - C++

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ MPI задача коммивояжера методом ветвей и границ http://www.cyberforum.ru/cpp/thread1637290.html
помогите реализацией задача коммивояжера методом ветвей и границ на вычислительном кластере. может у кого то готовая программа есть или пример
C++ Нужна программа Логическая игра "Маджонг" может кто делал Игра Маджонг, В ходе игры необходимо очистить игровое поле, убирая одинаковые фишки. удаляться они могут только в том случае, если у фишки открыта хотя бы одна сторона. Сложность игры зависит от... http://www.cyberforum.ru/cpp/thread1637074.html
C++ Скомпилировать OpenPegasus под Win32. C/C++
Помогите скомпилировать OpenPegasus под Win32. C/C++ Есть проект OpenPegasus под Unix. Нужно его скомпилировать под Windows. Цель - поднять WMI/Wbem service provider на Win32. Исходники...
Снимок с веб-камеры C++
нашел такой пример, который выводит на окно видео в режиме онлайн с вэб камеры http://pastebin.com/c9LCaLRT (из за ограничения количества символов в сообщении пришлось залить код на пастебин) ...
C++ При расшифровке RSA-сообщений на клиенте происходит ошибка http://www.cyberforum.ru/cpp/thread1634821.html
разрабатываю приложение на основе протокола подбрасывания честной монеты с использованием открытых и закрытых ключей, конкретно - ключей RSA. Сгенерила две пары ключей RSA, как и требуется, с...
C++ Написать программу для наххождения НОД, НОК Разработка Windows-приложения (в Qt) определения наибольшего общего делителя, наименьшего общего кратного и простых чисел для заданных больших чисел. Именно чтобы можно было вводить большие числа,... подробнее

Показать сообщение отдельно
hoggy
6690 / 2872 / 492
Регистрация: 15.11.2014
Сообщений: 6,461
Завершенные тесты: 1

[дизайн и эволюция] провалы в variadic конструкторы - C++

13.01.2016, 04:36. Просмотров 1133. Ответов 29
Метки (Все метки)

всем привет.

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

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

Часть 1.

проблема следующая:

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

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

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

следующий пример иллюстрирует эту проблему:
http://rextester.com/KDQKW21505
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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
struct example
{
    template<class... Args>
    example(Args&& ...args):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
 
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
int main()
{
    std::cout <<"hello, world\n";
 
    example ex1;
    // здесь все верно. 
    // дефолтный конструктор должен 
    // провалиться в вариадик
    assert(ex1.m_ctor==eVARIADIC);
 
    // здесь мы подразумевали конструктор копии
    // но вместо него провалились 
    // в вариадик конструктор
    example ex2 = ex1;
    assert(ex2.m_ctor==eCOPY);
}
для начала давайте разберемся в причинах провала.

стандарт гарантирует, что обычные сишные функции-элипсисы
(функции с переменным количеством аргументов,
вида: return_type func( ... ); )

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

однако, по не понятной для меня причине,
тоже самое для вариадиков не сделали.

в отличие от сишного элипсиса,
вариадики участвуют в конкурсе на общих основаниях.

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

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

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

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

http://rextester.com/QKP5835

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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
struct example
{
    template<class... Args>
    example(Args&& ...args):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
    
    // --- это "не стандартный" конструктор копии
    // вообще то, так делать не стоит
    // потому что можно огребсти нежданчиков
    // но для нашего примера сойдет
    example(example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
 
int main()
{
    std::cout <<"hello, world\n";
 
    example ex1;
    // здесь все верно. 
    // дефолтный конструктор должен 
    // провалиться в вариадик
    assert(ex1.m_ctor==eVARIADIC);
 
    // сработал нестандартный конструктор копии
    example ex2(ex1);
    assert(ex2.m_ctor==eCOPY);
}
какие можно сделать выводы?

1.
самое главное, это нужно понять:
что квалификатор const - неотъемлимая часть типа.

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

2.
если тип аргумента без каста не подходит к параметру не шаблоной функции,
то она уже проигрывает конкурс шаблонной.

Добавлено через 16 секунд
Часть 2.

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

как этого можно достичь?

есть два пути решения этой проблемы:

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

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

2.
техника sfinae.
это более сложный, зато - грамотный, и надежный.
именно так эта проблема решается в профессиональных библиотеках,
например - в boost.

рассмотрим его поподробнее:

однако, сначала, для упрощения материала,
мы рассмотрим его на примере с одним шабонно-параметром:

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

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

и он находит конструктор копии,
или перемещения.

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

пример:
http://rextester.com/SOAZP16444

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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
namespace detail{
    
    // --- предназначен для решения проблемы 
    // вызовов конструкторов в ситуации, 
    // когда любые аргументы проваливаются 
    // в вариадик-конструкторе
    template<class D, class B> class selector
    {
        typedef typename std::remove_reference<
            typename std::remove_reference<D>::type
        >::type
            type1;
            
        typedef typename std::remove_reference<
            typename std::remove_reference<B>::type
        >::type
            type2;
 
    public:
        enum { eOTHER = !std::is_same<type1, type2>::value };
    };
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
    
}//namespace detail
 
struct example
{
    template<class A, detail::for_other<A, example> = nullptr >
    example(A&& a):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
 
int main()
{
    std::cout <<"hello, world\n";
 
    example ex1(10);
    assert(ex1.m_ctor==eVARIADIC);
 
    // теперь все хорошо
    example ex2 = ex1;
    assert(ex2.m_ctor==eCOPY);
}
однако, этого не достаточно.
нужно тоже самое сделать и для конструктора перемещения тоже.

давайте модернизируем нашу sfinae-резалку:
http://rextester.com/FVAU27980

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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
namespace detail{
    
    // --- предназначен для решения проблемы 
    // вызовов конструкторов в ситуации, 
    // когда любые аргументы проваливаются 
    // в вариадик-конструкторе
    template<class D, class B> class selector
    {
        typedef typename std::remove_reference<
            typename std::remove_reference<D>::type
        >::type
            type1;
            
        typedef typename std::remove_reference<
            typename std::remove_reference<B>::type
        >::type
            type2;
 
        enum { eRVALUE = std::is_rvalue_reference<D>::value };
        
    public:
        enum { eIS_COPY   = std::is_same<type1, type2>::value  };
        enum { eIS_RVALUE = eRVALUE && eIS_COPY                };
        enum { eOTHER     = !eIS_RVALUE && !eIS_COPY           };
    };
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
    
}//namespace detail
 
struct example
{
    template<class A, detail::for_other<A&&, example> = nullptr >
    example(A&& a):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
// что бы поломать компилятору оптимизацию перемещения
// потребуем от компилятора явного перемещения 
// вот так ручное вмешательство 
// иногда только мешает компилятору оптимизировать, хе-хе
example rvalue() {  return std::move(example(10)); }
 
int main()
{
    std::cout <<"hello, world\n";
 
    example ex1(10);
    assert(ex1.m_ctor==eVARIADIC);
 
    // теперь все хорошо
    example ex2 = ex1;
    assert(ex2.m_ctor==eCOPY);
    
    example ex3 = rvalue();
    assert(ex3.m_ctor==eRVALUE);
}
теперь вроде бы удалось внести порядок в работу конструкторов.
однако, для полноценной работы этого ещё не достаточно.

данная конструкция сломается сразу же,
как только возникнет наследование:

http://rextester.com/BZRR35485

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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
namespace detail{
    
    // --- предназначен для решения проблемы 
    // вызовов конструкторов в ситуации, 
    // когда любые аргументы проваливаются 
    // в вариадик-конструкторе
    template<class D, class B> class selector
    {
        typedef typename std::remove_reference<
            typename std::remove_reference<D>::type
        >::type
            type1;
            
        typedef typename std::remove_reference<
            typename std::remove_reference<B>::type
        >::type
            type2;
 
        enum { eRVALUE = std::is_rvalue_reference<D>::value };
        
    public:
        enum { eIS_COPY   = std::is_same<type1, type2>::value  };
        enum { eIS_RVALUE = eRVALUE && eIS_COPY                };
        enum { eOTHER     = !eIS_RVALUE && !eIS_COPY           };
    };
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
    
}//namespace detail
 
struct example
{
    template<class A, detail::for_other<A&&, example> = nullptr >
    example(A&& a):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
 
struct der: example
{
    der(const int a):example(a){}
    der(const der& rhs):example(rhs){}
    der(der&& rhs):example(std::move(rhs)){}
};
 
der rvalue() {  return std::move(der(10)); }
 
int main()
{
    std::cout <<"hello, world\n";
 
    der d1(10);
    assert(d1.m_ctor==eVARIADIC);
 
    // сломались
    der d2 = d1;
    assert(d2.m_ctor==eCOPY);
    
}
это связанно с тем, что в обычной жизни
наследник без проблем кастится к базовому.

но в нашем случае любой каст - это провал в шаблонно-конструктор.

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

для этого модернизируем нашу конструкцию:

http://rextester.com/OEBD97935

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
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eCOPY, eRVALUE };
 
 
namespace detail{
    
    // --- предназначен для решения проблемы 
    // вызовов конструкторов в ситуации, 
    // когда любые аргументы проваливаются 
    // в вариадик-конструкторе
    template<class D, class B> class selector
    {
        typedef typename std::remove_reference<
            typename std::remove_reference<D>::type
        >::type
            derrived_t;
            
        typedef typename std::remove_reference<
            typename std::remove_reference<B>::type
        >::type
            base_t;
 
        enum { 
            eIS_DERRIVED = std::is_base_of<base_t, derrived_t>::value           
        };
 
        enum { eIS_RVALUE = std::is_rvalue_reference<D>::value };
 
    public:
        enum { eCOPY   =  eIS_DERRIVED && !eIS_RVALUE };
        enum { eRVALUE =  eIS_DERRIVED &&  eIS_RVALUE };
        enum { eOTHER  = !eIS_DERRIVED                };
    };
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
}//namespace detail
 
struct example
{
    template<class A, detail::for_other<A&&, example> = nullptr >
    example(A&& a):m_ctor(eVARIADIC)
        { std::cout <<"variadic\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout <<"copy\n"; }
    example(example&&):m_ctor(eRVALUE)
        { std::cout <<"rvalue\n"; }
 
    eCTOR m_ctor;
};
 
 
struct der: example
{
    der(const int a):example(a){}
    der(const der& rhs):example(rhs){}
    der(der&& rhs):example(std::move(rhs)){}
};
 
der rvalue() {  return std::move(der(10)); }
 
int main()
{
    std::cout <<"hello, world\n";
 
    der d1(10);
    assert(d1.m_ctor==eVARIADIC);
 
    // сломались
    der d2 = d1;
    assert(d2.m_ctor==eCOPY);
    
}
как видите, теперь все заработало.
и это - хорошая новость.
15
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru