Форум программистов, компьютерный форум CyberForum.ru

C++

Войти
Регистрация
Восстановить пароль
 
 
hoggy
6428 / 2646 / 458
Регистрация: 15.11.2014
Сообщений: 5,834
Завершенные тесты: 1
#1

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

13.01.2016, 04:36. Просмотров 1064. Ответов 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);
    
}
как видите, теперь все заработало.
и это - хорошая новость.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
hoggy
6428 / 2646 / 458
Регистрация: 15.11.2014
Сообщений: 5,834
Завершенные тесты: 1
13.01.2016, 04:36  [ТС]     [дизайн и эволюция] провалы в variadic конструкторы #2
Часть 3.

способ для 1 параметра шаблона отлично работает.
и это - хорошая новость.

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

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

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

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

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


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

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

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

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

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

итак, знакомьтесь с новой техникой с++11:
делегирующие конструкторы:

http://rextester.com/ADMR12889

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include <iostream>
#include <cassert>
 
enum eCTOR{ eNONE, eVARIADIC, eONE, 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_copy 
            = typename std::enable_if< selector<D,B>::eCOPY>::type*;
 
    template<class D, class B>
        using for_rvalue 
            = typename std::enable_if< selector<D,B>::eRVALUE>::type*;
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
}//namespace detail
 
struct example
{
        #define dRVALUE detail::for_rvalue<A&&, example> = nullptr
        #define dCOPY   detail::for_copy  <A&&, example> = nullptr
        #define dANY    detail::for_other <A&&, example> = nullptr
 
        template<class... Args> 
        example(Args&&... args):m_ctor(eVARIADIC)
            { std::cout << "variadic\n"; }
 
    // --- тоже самое, что вариадик, только для 1 параметра
        template<class A, dANY> 
        example(A&& a):m_ctor(eONE)
            { std::cout << "one\n"; }
 
    // --- перенаправление на констурктор копии
        template<class A, dCOPY> 
            example(A&& rhs): example(static_cast<const example&>(rhs))
            {}
    
    // --- перенаправление на констурктор перемещения
        template<class A, dRVALUE> example(A&& rhs): 
            example(std::move(static_cast<example&&>(rhs)))
            {}
 
        example(const example&):m_ctor(eCOPY)
            { std::cout << "copy\n"; }
    
        example(example&& ):m_ctor(eRVALUE)
            { std::cout << "move\n"; }
 
        #undef dRVALUE 
        #undef dCOPY   
        #undef dANY    
 
    eCTOR m_ctor;
};
 
 
struct der: example
{
    der(const int a):example(a){}
    der(const int a, const int b):example(a,b){}
    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,20);
    assert(d1.m_ctor==eVARIADIC);
    
    der d2(10);
    assert(d2.m_ctor==eONE);
 
    der d3 = d1;
    assert(d3.m_ctor==eCOPY);
    
    der d4 = rvalue();
    assert(d4.m_ctor==eRVALUE);
    
}
вот так: немножко там шаблончик, тут макросик,
и вуаля! вариадик взят под контроль.

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

лично я для подобных целей завел библиотеку
под названием "костыль" "workground"
что в переводе на русски, должно означать "обходной путь":

итоговое решение:

http://rextester.com/FUHG59360

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#include <iostream>
#include <cassert>
 
// =====================================================================================
// =====================================================================================
// =====================================================================================
// весь этот ужас скрыт в библиотеке "workground"
//
// workground.h
//
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_copy 
            = typename std::enable_if< selector<D,B>::eCOPY>::type*;
 
    template<class D, class B>
        using for_rvalue 
            = typename std::enable_if< selector<D,B>::eRVALUE>::type*;
 
    template<class D, class B>
        using for_other
            = typename std::enable_if< selector<D,B>::eOTHER>::type*;
    
    #define dFOR_CONSTRUCTOR_(situation, type_) \
        detail::for_##situation<A&&, type_> = nullptr
    
    #define dWITH_REDIRECT_CONSTRUCTORS(type_)                \
        template<class A, dFOR_CONSTRUCTOR_(copy, type_) >    \
            type_(A&& rhs)                                    \
                : type_(static_cast<const type_&>(rhs))       \
            {}                                                \
                                                              \
        template<class A, dFOR_CONSTRUCTOR_(rvalue, type_) >  \
            type_(A&& rhs)                                    \
                : type_(std::move(static_cast<type_&&>(rhs))) \
            {}                                                \
                                                              \
        template<class A, dFOR_CONSTRUCTOR_(other, type_)>    \
            type_(A&& a)
    
}//namespace detail
 
 
// =====================================================================================
// =====================================================================================
// =====================================================================================
 
// вот это все, что полагается видеть пользователю:
 
enum eCTOR{ eNONE, eVARIADIC, eONE, eCOPY, eRVALUE };
 
struct example
{
    template<class... Args> 
    example(Args&&... args):m_ctor(eVARIADIC)
        { std::cout << "variadic\n"; }
 
    dWITH_REDIRECT_CONSTRUCTORS(example):m_ctor(eONE)
        { std::cout << "one\n"; }
 
    example(const example&):m_ctor(eCOPY)
        { std::cout << "copy\n"; }
 
    example(example&& ):m_ctor(eRVALUE)
        { std::cout << "move\n"; }
 
    eCTOR m_ctor;
};
 
// =====================================================================================
// =====================================================================================
// =====================================================================================
 
// теперь, если пользователь захочет отнаследоваться,
// то ему вообще не обязательно думать о том ужассе
// что на самом деле таится в базовом классе
 
struct der: example
{
    der(const int a):example(a){}
    der(const int a, const int b):example(a,b){}
    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,20);
    assert(d1.m_ctor==eVARIADIC);
    
    der d2(10);
    assert(d2.m_ctor==eONE);
 
    der d3 = d1;
    assert(d3.m_ctor==eCOPY);
    
    der d4 = rvalue();
    assert(d4.m_ctor==eRVALUE);
}
здесь ещё могут быть какие то редкие случаи,
которые данный код не учитывает.
например: const rvalue,
ну или volatile какие нибудь.

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

к тому же, архитектура selector`a специально ориентирована
на возможные расширения.

поэтому, вы всегда сможете его модернизировать,
и обыграть какой нибудь дополнительный кейс.
Kastaneda
Форумчанин
Эксперт С++
4511 / 2853 / 227
Регистрация: 12.12.2009
Сообщений: 7,249
Записей в блоге: 1
Завершенные тесты: 1
13.01.2016, 10:28     [дизайн и эволюция] провалы в variadic конструкторы #3
hoggy, а для чего 2 раза remove_reference в этих конструкциях
C++
1
2
3
4
        typedef typename std::remove_reference<
            typename std::remove_reference<D>::type
        >::type
            type1;
?

Добавлено через 2 часа 24 минуты

Не по теме:

hoggy, я не с целью очередной спор затеять, я действительно спрашиваю

ct0r
Игогошка!
1768 / 670 / 42
Регистрация: 19.08.2012
Сообщений: 1,284
Завершенные тесты: 1
13.01.2016, 10:40     [дизайн и эволюция] провалы в variadic конструкторы #4
hoggy, на кой фиг нужны вариадик конструкторы и весь этот костыль, если можно написать конструктор, который просто принимает std::initializer_list<T>?
Croessmah
Модератор
Эксперт CЭксперт С++
13052 / 7315 / 814
Регистрация: 27.09.2012
Сообщений: 18,052
Записей в блоге: 3
Завершенные тесты: 1
13.01.2016, 10:48     [дизайн и эволюция] провалы в variadic конструкторы #5
Цитата Сообщение от ct0r Посмотреть сообщение
который просто принимает std::initializer_list<T>?
ну так в вариадике параметры разных типов, тогда как в initializer_list только T
rikimaru2013
C++ Game Dev
2419 / 1113 / 240
Регистрация: 30.11.2013
Сообщений: 3,660
13.01.2016, 10:50     [дизайн и эволюция] провалы в variadic конструкторы #6
Цитата Сообщение от ct0r Посмотреть сообщение
который просто принимает std::initializer_list<T>
вроде бы при вариадик типы разные, а в иниц листе один на всех.
mporro
256 / 102 / 14
Регистрация: 04.07.2014
Сообщений: 571
13.01.2016, 11:01     [дизайн и эволюция] провалы в variadic конструкторы #7
Variadic constructor -- это уже откровенный провал дизайна, ибо любой конструктор -- это преобразование типов, коих можно написать счётное число. В классе им не место. В классе достаточно явно описать один, конструктор-инициализатор, для остального есть фабрики.

Хотя, наверно, с точки зрения возможностей C++11 поковырять подобную ситуацию интересно...
ct0r
Игогошка!
1768 / 670 / 42
Регистрация: 19.08.2012
Сообщений: 1,284
Завершенные тесты: 1
13.01.2016, 11:05     [дизайн и эволюция] провалы в variadic конструкторы #8
Цитата Сообщение от Croessmah Посмотреть сообщение
ну так в вариадике параметры разных типов, тогда как в initializer_list только T
Цитата Сообщение от rikimaru2013 Посмотреть сообщение
вроде бы при вариадик типы разные, а в иниц листе один на всех.
Ага. Ну тогда, если уж охота, то можно написать вариадик конструктор, принимающий несколько списков инициализации Не?
Какие вообще use-cases? С этого стоило бы начать.
rikimaru2013
C++ Game Dev
2419 / 1113 / 240
Регистрация: 30.11.2013
Сообщений: 3,660
13.01.2016, 11:10     [дизайн и эволюция] провалы в variadic конструкторы #9
Цитата Сообщение от ct0r Посмотреть сообщение
Какие вообще use-cases? С этого стоило бы начать.
Мне б тоже интересно было услышать бы) Пока что сложно представить себе класс, который говорит "пихай в меня всё, что хочешь (включая сложные конструкции кстате) и я всё съем и создамся.
Croessmah
Модератор
Эксперт CЭксперт С++
13052 / 7315 / 814
Регистрация: 27.09.2012
Сообщений: 18,052
Записей в блоге: 3
Завершенные тесты: 1
13.01.2016, 11:12     [дизайн и эволюция] провалы в variadic конструкторы #10
Цитата Сообщение от ct0r Посмотреть сообщение
принимающий несколько списков инициализации
а как потом весь этот мусор разгребать?
Да и сколько должно быть этих списков?
Ну, собственно, согласен mporro.
Думаю, такой костыль не стоит усилий,
потраченных на него, хотя хз что может приключиться.

Но в любом случае, спасибо hoggy за весьма интересную тему,
коих на форуме почти никогда не бывает.
В основном тут "памогите решить, утром зачет, а задания дали только ночью, некада читать первую главу книги".
Так что в любом случае плюсик автору.
ct0r
Игогошка!
1768 / 670 / 42
Регистрация: 19.08.2012
Сообщений: 1,284
Завершенные тесты: 1
13.01.2016, 11:17     [дизайн и эволюция] провалы в variadic конструкторы #11
Цитата Сообщение от Croessmah Посмотреть сообщение
а как потом весь этот мусор разгребать?
Ну а как ты у себя в квартире разгребаешь? Абсолютно тот же подход

Цитата Сообщение от hoggy Посмотреть сообщение
уже несколько человек обращались ко мне по почте,
Жду деанонимизации и use-cas'ы от вас, человеки
Croessmah
Модератор
Эксперт CЭксперт С++
13052 / 7315 / 814
Регистрация: 27.09.2012
Сообщений: 18,052
Записей в блоге: 3
Завершенные тесты: 1
13.01.2016, 11:28     [дизайн и эволюция] провалы в variadic конструкторы #12
Цитата Сообщение от ct0r Посмотреть сообщение
Абсолютно тот же подход
Не, камаз с трактором тут не прокатят
Kastaneda
Форумчанин
Эксперт С++
4511 / 2853 / 227
Регистрация: 12.12.2009
Сообщений: 7,249
Записей в блоге: 1
Завершенные тесты: 1
13.01.2016, 12:00     [дизайн и эволюция] провалы в variadic конструкторы #13
По вопросу использования - как вариант можно дергать делегирующие конструкторы, которые будут дергать другие делегирующие конструкторы, таким образом программист может регулировать порядок инициализации членов передавая в конструктор аргументы в разном порядке.
И еще про "зачем это надо" - если бы добавили вариадики, но не добавили возможность конструкторам их использовать, то на всех С++ ресурсах было бы много негодования на тему "почему везде можно, а в конструкторе нет", не смотря на то, что пользы от этого мало
ct0r
Игогошка!
1768 / 670 / 42
Регистрация: 19.08.2012
Сообщений: 1,284
Завершенные тесты: 1
13.01.2016, 15:34     [дизайн и эволюция] провалы в variadic конструкторы #14
Цитата Сообщение от Kastaneda Посмотреть сообщение
По вопросу использования - как вариант можно дергать делегирующие конструкторы, которые будут дергать другие делегирующие конструкторы, таким образом программист может регулировать порядок инициализации членов передавая в конструктор аргументы в разном порядке.
Не совсем понял. Звучит все же так, как будто это не нужно Можно пример кода?

Цитата Сообщение от Kastaneda Посмотреть сообщение
а для чего 2 раза remove_reference в этих конструкциях
Ни для чего. Достаточно одного раза.

Цитата Сообщение от hoggy Посмотреть сообщение
"workground"
что в переводе на русски, должно означать "обходной путь":
Только не workGround, а workAround. Я заметил, что это не опечатка

Цитата Сообщение от Croessmah Посмотреть сообщение
Не, камаз с трактором тут не прокатят
Пентхаус? Раскулачивать пора

Цитата Сообщение от Kastaneda Посмотреть сообщение
И еще про "зачем это надо" - если бы добавили вариадики, но не добавили возможность конструкторам их использовать, то на всех С++ ресурсах было бы много негодования на тему "почему везде можно, а в конструкторе нет", не смотря на то, что пользы от этого мало
Ну мне приходят на ум случаи, когда вариадик конструкторы могут пригодиться, но чтобы при этом нужен был особенный конструктор копирования или в этом роде

Я немного пошукал в инете:
1) Если можно обойтись списком инициализации, то так и лучше делать.
2) Этот конфликт между конструкторами предлагалось разрешить, но комитет сказал: есть sfinae, а скоро будут концепты, поэтому нафиг.
3) Ребята на stackoverflow считают, что нестандартные конструкторы предпочтительнее sfinae.

PS Я особо не разбирался, сейчас времени нет, но если написать вот так, то конкретно в данном примере выдаст точно такой же результат (вариадик не работает, если аргументов нет или есть один аргумент, который кастится):
C++
1
2
3
4
        template<typename Arg, typename ...Args, typename =
          std::enable_if_t<
            !(sizeof...(Args) == 0 && std::is_convertible<Arg, example>::value)>
        >
Добавлено через 19 минут
Цитата Сообщение от Croessmah Посмотреть сообщение
Да и сколько должно быть этих списков?
Сколько хочешь. Вариадик конструкторы же ж. Тут есть свои ограничения, но ты можешь зато эти списки в рантайме формировать, что плюс. Все зависит от конкретных условий.
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
13.01.2016, 16:25     [дизайн и эволюция] провалы в variadic конструкторы
Еще ссылки по теме:
Variadic templates C++
C++ Variadic templates
C++ Variadic templates fail
C++ recursion with variadic template
C++ Result_of + variadic templates

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

Или воспользуйтесь поиском по форуму:
Croessmah
Модератор
Эксперт CЭксперт С++
13052 / 7315 / 814
Регистрация: 27.09.2012
Сообщений: 18,052
Записей в блоге: 3
Завершенные тесты: 1
13.01.2016, 16:25     [дизайн и эволюция] провалы в variadic конструкторы #15
Цитата Сообщение от ct0r Посмотреть сообщение
Пентхаус? Раскулачивать пора
Не, просто бардак. У меня на компутерном столе даже кеторол со спиртом есть
Yandex
Объявления
13.01.2016, 16:25     [дизайн и эволюция] провалы в variadic конструкторы
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru