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

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

Войти
Регистрация
Восстановить пароль
Другие темы раздела
C++ Вывести 10 самых длинных (по числу символов) предложений http://www.cyberforum.ru/cpp/thread1637394.html
Ребят программисты помогите, понимаю что программа легкая. Но не могу сделать. Текст надо считать из файла.
C++ Найти причины и способы устранения ошибки Во время откладки указатель на число выдает мусор типо -81791524 #include <stdlib.h> #include <time.h> #include <iostream> #include <conio.h> #include <cctype> using::std::cin; using::std::cout; using::std::endl; http://www.cyberforum.ru/cpp/thread1637383.html
Как лучше всего пробежать все элементы контейнера? C++
Речь о следующем. Есть vector. Я хочу пробежать все его элементы, но походу я буду проверять удовлетворяют они определенному условию или нет. Если да, то этот элемент удаляется. Как это лучше всего реализовать?
C++ Найти сумму ряда
Ребят, голову ломаю и никак не осилю. В чем смысл вообще? Я понять формулу даже не могу, не то что уж код написать здесь. Какие-то "-...+". Что это вообще? Помогите кто чем может.
C++ Вычислить произведение отрицательных элементов массива http://www.cyberforum.ru/cpp/thread1637347.html
Приветствую вас дорогие форумчане. Прошу о помощи в изменении программы. В коде предоставленном ниже требуется заменить: рандомный подбор чисел на вводимый. Зарание спасибо! (Текст задания: В одномерном массиве, состоящем из n вещественных элементов, вычислить: 1.) произведение отрицательных элементов массива; 2.) сумму положительных элементов массива, расположенных до максимального элемента....
C++ Реализовать структуру "Student" Создайте программу для ввода и вывода фамилий и оценок студентов. Введите не менее 10 студентов. Отберите студентов, у которых вторая и четвертая оценка 2 подробнее

Показать сообщение отдельно
hoggy
6157 / 2523 / 443
Регистрация: 15.11.2014
Сообщений: 5,564
Завершенные тесты: 1

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

13.01.2016, 04:36. Просмотров 1026. Ответов 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);
    
}
как видите, теперь все заработало.
и это - хорошая новость.
После регистрации реклама в сообщениях будет скрыта и будут доступны все возможности форума.
 
Текущее время: 20:50. Часовой пояс GMT +3.
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru