Форум программистов, компьютерный форум, киберфорум
C++
Войти
Регистрация
Восстановить пароль
 
 
Рейтинг 4.73/11: Рейтинг темы: голосов - 11, средняя оценка - 4.73
Эксперт С++
8404 / 4080 / 891
Регистрация: 15.11.2014
Сообщений: 9,172
1

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

13.01.2016, 04:36. Показов 1971. Ответов 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
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
13.01.2016, 04:36
Ответы с готовыми решениями:

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

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

Variadic templates, или variadic constructor в шаблоне, или прочие извращения
Здравствуйте. Есть такое Wrapper&lt;Obj&gt; w; Wrapper - обертка над объектом того класса, который...

Variadic Templates - как обращаться к аргументам variadic-функции?
Всем привет! Наверное, рано мне ещё с моими скудными знаниями в это лезть, но, изучив шаблоны и...

29
Эксперт С++
8404 / 4080 / 891
Регистрация: 15.11.2014
Сообщений: 9,172
13.01.2016, 04:36  [ТС] 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 специально ориентирована
на возможные расширения.

поэтому, вы всегда сможете его модернизировать,
и обыграть какой нибудь дополнительный кейс.
10
Jesus loves me
Эксперт С++
5173 / 3146 / 354
Регистрация: 12.12.2009
Сообщений: 7,947
Записей в блоге: 2
13.01.2016, 10:28 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, я не с целью очередной спор затеять, я действительно спрашиваю :)

0
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
13.01.2016, 10:40 4
hoggy, на кой фиг нужны вариадик конструкторы и весь этот костыль, если можно написать конструктор, который просто принимает std::initializer_list<T>?
0
Don't worry, be happy
17143 / 10026 / 1933
Регистрация: 27.09.2012
Сообщений: 24,956
Записей в блоге: 1
13.01.2016, 10:48 5
Цитата Сообщение от ct0r Посмотреть сообщение
который просто принимает std::initializer_list<T>?
ну так в вариадике параметры разных типов, тогда как в initializer_list только T
0
2542 / 1201 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
13.01.2016, 10:50 6
Цитата Сообщение от ct0r Посмотреть сообщение
который просто принимает std::initializer_list<T>
вроде бы при вариадик типы разные, а в иниц листе один на всех.
0
306 / 101 / 18
Регистрация: 04.07.2014
Сообщений: 571
13.01.2016, 11:01 7
Variadic constructor -- это уже откровенный провал дизайна, ибо любой конструктор -- это преобразование типов, коих можно написать счётное число. В классе им не место. В классе достаточно явно описать один, конструктор-инициализатор, для остального есть фабрики.

Хотя, наверно, с точки зрения возможностей C++11 поковырять подобную ситуацию интересно...
0
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
13.01.2016, 11:05 8
Цитата Сообщение от Croessmah Посмотреть сообщение
ну так в вариадике параметры разных типов, тогда как в initializer_list только T
Цитата Сообщение от rikimaru2013 Посмотреть сообщение
вроде бы при вариадик типы разные, а в иниц листе один на всех.
Ага. Ну тогда, если уж охота, то можно написать вариадик конструктор, принимающий несколько списков инициализации Не?
Какие вообще use-cases? С этого стоило бы начать.
1
2542 / 1201 / 358
Регистрация: 30.11.2013
Сообщений: 3,826
13.01.2016, 11:10 9
Цитата Сообщение от ct0r Посмотреть сообщение
Какие вообще use-cases? С этого стоило бы начать.
Мне б тоже интересно было услышать бы) Пока что сложно представить себе класс, который говорит "пихай в меня всё, что хочешь (включая сложные конструкции кстате) и я всё съем и создамся.
0
Don't worry, be happy
17143 / 10026 / 1933
Регистрация: 27.09.2012
Сообщений: 24,956
Записей в блоге: 1
13.01.2016, 11:12 10
Цитата Сообщение от ct0r Посмотреть сообщение
принимающий несколько списков инициализации
а как потом весь этот мусор разгребать?
Да и сколько должно быть этих списков?
Ну, собственно, согласен mporro.
Думаю, такой костыль не стоит усилий,
потраченных на него, хотя хз что может приключиться.

Но в любом случае, спасибо hoggy за весьма интересную тему,
коих на форуме почти никогда не бывает.
В основном тут "памогите решить, утром зачет, а задания дали только ночью, некада читать первую главу книги".
Так что в любом случае плюсик автору.
0
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
13.01.2016, 11:17 11
Цитата Сообщение от Croessmah Посмотреть сообщение
а как потом весь этот мусор разгребать?
Ну а как ты у себя в квартире разгребаешь? Абсолютно тот же подход

Цитата Сообщение от hoggy Посмотреть сообщение
уже несколько человек обращались ко мне по почте,
Жду деанонимизации и use-cas'ы от вас, человеки
0
Don't worry, be happy
17143 / 10026 / 1933
Регистрация: 27.09.2012
Сообщений: 24,956
Записей в блоге: 1
13.01.2016, 11:28 12
Цитата Сообщение от ct0r Посмотреть сообщение
Абсолютно тот же подход
Не, камаз с трактором тут не прокатят
0
Jesus loves me
Эксперт С++
5173 / 3146 / 354
Регистрация: 12.12.2009
Сообщений: 7,947
Записей в блоге: 2
13.01.2016, 12:00 13
По вопросу использования - как вариант можно дергать делегирующие конструкторы, которые будут дергать другие делегирующие конструкторы, таким образом программист может регулировать порядок инициализации членов передавая в конструктор аргументы в разном порядке.
И еще про "зачем это надо" - если бы добавили вариадики, но не добавили возможность конструкторам их использовать, то на всех С++ ресурсах было бы много негодования на тему "почему везде можно, а в конструкторе нет", не смотря на то, что пользы от этого мало
1
Игогошка!
1801 / 708 / 44
Регистрация: 19.08.2012
Сообщений: 1,367
13.01.2016, 15:34 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 Посмотреть сообщение
Да и сколько должно быть этих списков?
Сколько хочешь. Вариадик конструкторы же ж. Тут есть свои ограничения, но ты можешь зато эти списки в рантайме формировать, что плюс. Все зависит от конкретных условий.
3
Don't worry, be happy
17143 / 10026 / 1933
Регистрация: 27.09.2012
Сообщений: 24,956
Записей в блоге: 1
13.01.2016, 16:25 15
Цитата Сообщение от ct0r Посмотреть сообщение
Пентхаус? Раскулачивать пора
Не, просто бардак. У меня на компутерном столе даже кеторол со спиртом есть
1
15043 / 8062 / 1940
Регистрация: 30.01.2014
Сообщений: 13,663
13.01.2016, 19:28 16
Цитата Сообщение от ct0r Посмотреть сообщение
пример кода
Наверное не совсем то, о чем он говорил, но с ними (с вариадик конструкторами) можно, например, вот такие штуки проворачивать (ключевое выделил комментарием):
Кликните здесь для просмотра всего текста

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
#include <cstdio>
#include <tuple>
#include <utility>
#include <functional>
 
template <typename ...F>
class C
{
public:
    C(std::tuple<F...> const & args)
        : C(args, std::make_index_sequence<sizeof...(F)>{})
    { }
    
    void operator()() const
    {
        for(auto && f : m_funcs)
            f();
    }
    
private:
    template <typename R, typename ...Args>
    static auto autobind(R (f)(Args...))
    {
        return std::bind(f, Args()...);
    }
  
    template <size_t ...Indices>
    C(std::tuple<F...> const & args, std::index_sequence<Indices...>) // <---- здесь
        : m_funcs{ autobind(std::get<Indices>(args))... }
    { }
        
    std::function<void()> m_funcs[sizeof...(F)];
};
 
template <typename ...F>
auto make_funclist(F ...f)
{
    return C<F...>{ std::make_tuple(f...) };
}
 
void f1()    
{ std::printf("1\n"); }
void f2(int) 
{ std::printf("2\n"); }
void f3(int, double) 
{ std::printf("3\n"); }
 
int main()
{
    make_funclist(f1, f2, f3)();
}
http://rextester.com/YXXUI48392
6
Kastaneda
13.01.2016, 19:54
  #17

Не по теме:

DrOffset, вместо тысячи слов

Кликните здесь для просмотра всего текста
[дизайн и эволюция] провалы в variadic конструкторы

:D

0
Эксперт С++
8404 / 4080 / 891
Регистрация: 15.11.2014
Сообщений: 9,172
13.01.2016, 20:26  [ТС] 18
Цитата Сообщение от Kastaneda Посмотреть сообщение
а для чего 2 раза remove_reference в этих конструкциях
когда то я наступил на какие то грабли,
универсальная ссылка схлопывалась до простого референса.
сейчас это, наверное, уже не актуально.

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

Цитата Сообщение от ct0r Посмотреть сообщение
std::initializer_list<T>
не умеет разные типы.

Цитата Сообщение от mporro Посмотреть сообщение
это уже откровенный провал дизайна
одна из наиболее важных фич с++11, на мой взгляд.
её ценность соизмерима с ценностью decltype,
на мой взгляд,

Цитата Сообщение от ct0r Посмотреть сообщение
Ну тогда, если уж охота, то можно написать вариадик конструктор, принимающий несколько списков инициализации Не?
нет.

это будут разные списки из одинаковых типов.
однако на практике требуются списки из разных типов.

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

Цитата Сообщение от ct0r Посмотреть сообщение
Какие вообще use-cases?
std::make_shared и тп вещи, например.

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

наприммер: диначический делегат.

имеет дизайн:

C++
1
2
// --- не шаблонный
connector con(obj, &some::method, 10,"ololo",true);
ему нужно уметь нацеливаться на любые функции,
методы, с учетом биндинга аргументов, и тп.

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

я такую штуку делал ещё под с++03.
вариадиков мне люто не хватало.

пришлось применить тяжелую артилерию на макросах.


ну или вот, тоже из личной практики,
паттерн "домики":

C++
1
2
3
4
5
6
7
auto root = widget::create<Ваш собственный виджет>(аргументы вашего виджета)
    .width(800)
    .heigth(600)
    .add<Ещё один виджет>(и его аргументы)
        .Label("trololo")
    .done()
.done();
есть некоторая базовая форма,
от которой наследуются другие формы.
базовая форма отвечает за механику построения дерева.

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

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

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

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

вообще, при желании,
примеров можно привести массу.

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

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

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

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

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

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

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

http://rextester.com/WNJIT27551

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
#include <type_traits>
#include <tuple>
#include <iostream>
#include <typeinfo>
 
 
template<class T, class... Args>
struct LastType
{
    typedef typename LastType<Args...>::Type
        Type;
};
 
template<class T> struct LastType<T>
    { typedef T Type; };
 
 
struct Alloc{};
    
#define dNOREF  \
    typename std::remove_reference<L>::type
 
#define dNOCONST \
    typename std::remove_cv< dNOREF >::type
 
#define dFOR_ALLOCATOR                             \
    class L = typename LastType<Args...>::Type,    \
        typename std::enable_if<                   \
            std::is_same< dNOCONST, Alloc>::value  \
        >::type* = nullptr
 
#define dFOR_NO_ALLOCATOR                          \
    class L = typename LastType<Args...>::Type,    \
        typename std::enable_if<                   \
            !std::is_same< dNOCONST, Alloc>::value \
        >::type* = nullptr
 
struct Example
{
    template<class T, class... Args, dFOR_ALLOCATOR>
    Example(T&&, Args&&... args)
    {
        std::cout << "WITH ALLOCATOR\n";
        enum { num = sizeof ...(Args) };
        const auto&& tuple_ = 
            std::forward_as_tuple(args...);
        SetAlloc( std::get<num - 1>(tuple_) );
    }
 
    template<class T, class... Args, dFOR_NO_ALLOCATOR>
    Example(T&&, Args&&... )
        { std::cout << "WITHOUT ALLOCATOR\n"; }
 
    Example()
        { std::cout << "WITHOUT ARGUMENTS\n"; }
 
    template<class T>
    void SetAlloc(T&& alloc)
    {
        std::cout << "set allocator: " 
            << typeid(alloc).name() 
            << std::endl;
    }
 
 
};
 
#undef dNOCONST
#undef dNOREF
 
#undef dFOR_NO_ALLOCATOR
#undef dFOR_ALLOCATOR
 
 
//----------------------------------------------
 
 
 
#include <iostream>
 
int main()
{
    std::cout << "Hello, world!\n";
    
    Alloc alloc;
    
    Example ex1(10, alloc);
    
    Example ex2(10, 20);
}
этот способ - наглядный пример-иллюстрация того,
как можно в шапке шаблона
анализировать строение вариадик-пака.

правда цена за это - явно указанный дефолтный конструктор.
3
42 / 42 / 17
Регистрация: 25.04.2014
Сообщений: 499
02.08.2016, 01:33 19
hoggy, я тут помозговал... вот вполне юзабельное решение , правда допиливать надо, ибо я так и не смог устранить проблему с конструктором копии:
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
#include <boost/mpl/if.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/find.hpp>
 
namespace variadic {
 
struct plasma {
    template<typename...Arg,
             typename = typename std::enable_if<
                            boost::mpl::find<
                                        typename boost::mpl::vector<std::decay<Arg>...>,
                                        plasma
                                            >::type::pos::value
                                        == boost::mpl::size<
                                                    typename boost::mpl::vector<Arg...>
                                                            >::type::value
                                      >::type*
             >
    plasma(Arg&&...args) {
        std::cout<<"variadic construct\n";
    }
    plasma() { std::cout<<"construct\n"; }
    plasma(const plasma& rhs) { std::cout<<"copy construct\n"; }
    plasma(plasma&& rhs) { std::cout<<"move construct\n"; }
};
 
}
 
int main() {
variadic::plasma pls("dfa",2,3.);
        const variadic::plasma plss = std::move(pls);  //если убрать const то не вызывается конструктор копии
        variadic::plasma plsss = plss;
        variadic::plasma plssss;
}
0
Don't worry, be happy
17143 / 10026 / 1933
Регистрация: 27.09.2012
Сообщений: 24,956
Записей в блоге: 1
02.08.2016, 08:21 20
Как вариант:
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
namespace details{
    template <class ...T> struct is_same_one;
        
    template <class T>
    struct is_same_one<T> {
        static constexpr bool value = false;
    };
     
    template <class T, class U, class... Args>
    struct is_same_one<T, U, Args...> {
        static constexpr bool value = (sizeof...(Args) == 0) && std::is_same<std::decay_t<T>, std::decay_t<U>>();//или is_base_of
    };
}
 
 
 
 
 
 
 
namespace variadic {
 
struct plasma {
    template<typename...Arg, typename = std::enable_if_t<!details::is_same_one<plasma, Arg...>::value, void>*>
    plasma(Arg&&...args) {
        std::cout<<"variadic construct\n" ;
    }
    plasma() { std::cout<<"construct\n"; }
    plasma(const plasma& rhs) { std::cout<<"copy construct\n"; }
    plasma(plasma&& rhs) { std::cout<<"move construct\n"; }
};
 
}
 
int main() {
        variadic::plasma pls(8);
        const variadic::plasma plss = std::move(pls);
        variadic::plasma plsss = plss;
        variadic::plasma plssss;
        variadic::plasma plsssss;
        variadic::plasma plssssss(std::move(plss));
        variadic::plasma plsssssss(std::move(plsssss));
}
http://rextester.com/IQQJ52309
1
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
02.08.2016, 08:21

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

Webpack собирает проект, а приложение говорит что мои конструкторы не конструкторы
Помогите пожалуйста, в едином файле (не билде) всё работает хорошо, как только начинаю отделять...

Провалы в работе wi-fi
Доброго времени суток, проблема в следующем: Имеем ноутбук Asus X550LB, сетевая карта Ralink...

Управление BLDC и провалы по напряжению
Добрый день! Собираю контроллер управления BLDC с датчиками Холла - собственно говоря меня...

Провалы скорости записи и чтения внешнего винчестера
Купили на работе два внешних винчестера My Passport Western Digital. К одному у меня претензий нет,...


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

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

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