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

C++

Войти
Регистрация
Восстановить пароль
 
 
hoggy
6717 / 2900 / 497
Регистрация: 15.11.2014
Сообщений: 6,525
Завершенные тесты: 1
#1

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

13.01.2016, 04:36. Просмотров 1148. Ответов 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
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
13.01.2016, 04:36
Здравствуйте! Я подобрал для вас темы с ответами на вопрос [дизайн и эволюция] провалы в variadic конструкторы (C++):

[дизайн и эволюция] перегрузка макросов - C++
Часть 0. Вместо предисловия. всем привет. недавно, для одной из моих задач, мне потребовалось иметь возможность перегрузить...

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

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

Перенаправление указателя при перегрузке оператора в variadic template - C++
Есть вот такой вот код (сделан в vs2014): #include &quot;stdafx.h&quot; #include &lt;iostream&gt; #include &lt;conio.h&gt; #include &lt;math.h&gt; ...

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

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

29
DrOffset
7377 / 4454 / 1009
Регистрация: 30.01.2014
Сообщений: 7,304
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 конструкторы

0
hoggy
6717 / 2900 / 497
Регистрация: 15.11.2014
Сообщений: 6,525
Завершенные тесты: 1
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
tapochka
40 / 40 / 8
Регистрация: 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
Croessmah
Ушел
Эксперт CЭксперт С++
13557 / 7707 / 872
Регистрация: 27.09.2012
Сообщений: 18,996
Записей в блоге: 3
Завершенные тесты: 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
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
02.08.2016, 10:07 #21
Всю тему не читал ( много букф ), может что-то пропустил, но почему бы не сделать как-то так:
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
#include <iostream>
 
struct foobar {
  
  template<typename...Args>
  foobar(Args...){ std::cout << "variadic" << std::endl;}
  
   
  foobar() {std::cout << "ctor" << std::endl;}
  foobar(const foobar&) {std::cout << "copy" << std::endl;}
  foobar(foobar&&) {std::cout << "move" << std::endl;}
  
};
 
template <>
foobar::foobar<const foobar&>(const foobar&) = delete;
 
template <>
foobar::foobar<foobar&&>(foobar&&) = delete;
 
 
int main()
{
    foobar f;
    foobar f1(1,2,3,4,5);
    foobar f2(f);
    foobar f3(std::move(f1));
    
  return 0;
}
http://ideone.com/WSZ9Rc
0
Croessmah
Ушел
Эксперт CЭксперт С++
13557 / 7707 / 872
Регистрация: 27.09.2012
Сообщений: 18,996
Записей в блоге: 3
Завершенные тесты: 1
02.08.2016, 10:16 #22
Цитата Сообщение от Voivoid Посмотреть сообщение
но почему бы не сделать как-то так
Потому что такая передача
Цитата Сообщение от Voivoid Посмотреть сообщение
C++
1
foobar(Args...)
будет не эффективна - копирование объектов,
а если сделать так:
C++
1
foobar(Args&&...)
то вариадик сожрет остальное и мы увидим в выводе
ctor
variadic
variadic
move
http://rextester.com/TOIVQ95724
0
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
02.08.2016, 10:25 #23
Цитата Сообщение от Croessmah Посмотреть сообщение
то вариадик сожрет остальное и мы увидим в выводе
Хм, ну, надо тогда добавить еще deleted специализацию для foobar& и придется видимо написать конструктор foobar& который делегирует вызов конструктору const foobar&

Добавлено через 1 минуту
Как-то так в общем: http://ideone.com/WSZ9Rc
0
Croessmah
Ушел
Эксперт CЭксперт С++
13557 / 7707 / 872
Регистрация: 27.09.2012
Сообщений: 18,996
Записей в блоге: 3
Завершенные тесты: 1
02.08.2016, 10:38 #24
Voivoid, как только появится, например, volatile, вариадик снова сожрет всё, вместо ошибки.
Или если копировать будем от наследника, то тоже вариадик всё сожрет:
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
#include <iostream>
 
struct foobar {
 
  template<typename...Args>
  foobar(Args&&...){ std::cout << "variadic" << std::endl;}
 
 
  foobar() {std::cout << "ctor" << std::endl;}
  foobar(foobar& f) : foobar(static_cast<const foobar&>(f)) {}
  foobar(const foobar&) {std::cout << "copy" << std::endl;}
  foobar(foobar&&) {std::cout << "move" << std::endl;}
 
};
 
 
struct X: public foobar{
    X() = default;
    X(const X &src):foobar(src)/*без приведения типа*/{}
};
 
 
template <>
foobar::foobar<foobar&>(foobar&) = delete;
 
template <>
foobar::foobar<const foobar&>(const foobar&) = delete;
 
template <>
foobar::foobar<foobar&&>(foobar&&) = delete;
 
 
int main()
{
    foobar f;
    foobar f1(1,2,3,4,5);
    volatile foobar f2= f;
    foobar f5(f2);//Сожрал
    foobar f3(std::move(f1));
    X x1;
    X x2(x1);//сожрал
}
http://rextester.com/XAHEA99396

В моем коде для поддержки такого случая
нужно будет использовать is_base_of вместо is_same:
http://rextester.com/NQJ1094
2
Voivoid
675 / 278 / 12
Регистрация: 31.03.2013
Сообщений: 1,339
02.08.2016, 10:40 #25
А, ну да, точно
0
hoggy
6717 / 2900 / 497
Регистрация: 15.11.2014
Сообщений: 6,525
Завершенные тесты: 1
02.08.2016, 11:48  [ТС] #26
Цитата Сообщение от Croessmah Посмотреть сообщение
Как вариант:
наследование не умеет:
http://rextester.com/NJA95237

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
#include <type_traits>
#include <iostream>
 
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>>();
    };
}
 
 
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"; }
};
    
    
struct der: plasma
{
    der(const int a):plasma(a){}
    der(const int a, const int b):plasma(a,b){}
    der(const der& rhs):plasma(rhs){}
    der(der&& rhs):plasma(std::move(rhs)){}
    
    der():plasma(){}
};    
 
}
 
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));
    
    {
        variadic::der pls(8);
        const variadic::der plss = std::move(pls);
        variadic::der plsss = plss;
        variadic::der plssss;
        variadic::der plsssss;
        variadic::der plssssss(std::move(plss));
        variadic::der plsssssss(std::move(plsssss));
        
    }
    
    
}
output:
variadic construct
move construct
copy construct
variadic construct
variadic construct
copy construct
move construct
variadic construct
variadic construct
variadic construct
variadic construct
variadic construct
variadic construct
variadic construct
Добавлено через 2 минуты
Цитата Сообщение от Croessmah Посмотреть сообщение
В моем коде для поддержки такого случая
нужно будет использовать is_base_of вместо is_same:
http://rextester.com/NQJ1094
интересный вариант)
0
Croessmah
Ушел
Эксперт CЭксперт С++
13557 / 7707 / 872
Регистрация: 27.09.2012
Сообщений: 18,996
Записей в блоге: 3
Завершенные тесты: 1
02.08.2016, 11:51 #27
Цитата Сообщение от hoggy Посмотреть сообщение
наследование не умеет
написано же в комментарии к коду is_base_of.
http://rextester.com/DQQKP67151

И зачем дефолтный конструктор закомментили?

Добавлено через 42 секунды
Цитата Сообщение от hoggy Посмотреть сообщение
интересный вариант
рассинхрон по времени
0
notAll
430 / 151 / 36
Регистрация: 27.05.2016
Сообщений: 407
Завершенные тесты: 2
02.08.2016, 14:45 #28
Вроде как можно упростить - это:
Кликните здесь для просмотра всего текста
C++
1
2
3
4
5
6
7
8
9
10
11
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>>();
    };
на это:
C++
1
2
3
4
5
6
7
8
9
template <typename T, typename U, typename ... Args>
constexpr bool is_base_of_one_v = (sizeof...(Args) == 0) && std::is_base_of<std::decay_t<T>, std::decay_t<U>>();
 
struct plasma {
    template<typename...Arg, typename = std::enable_if_t<!is_base_of_one_v<plasma, Arg...>>*>
    plasma(Arg&&...args) {
        std::cout<<"variadic construct\n" ;
    }
...
0
Croessmah
Ушел
Эксперт CЭксперт С++
13557 / 7707 / 872
Регистрация: 27.09.2012
Сообщений: 18,996
Записей в блоге: 3
Завершенные тесты: 1
02.08.2016, 15:05 #29
Цитата Сообщение от notAll Посмотреть сообщение
на это
На том компиляторе, что у меня сейчас активен - этого не сделать.
На более новом можно
0
hoggy
6717 / 2900 / 497
Регистрация: 15.11.2014
Сообщений: 6,525
Завершенные тесты: 1
02.08.2016, 22:10  [ТС] #30
Цитата Сообщение от Croessmah Посмотреть сообщение
И зачем дефолтный конструктор закомментили?
что б убедиться, что и без него все работает.

многие классы не располагают дефолтным конструктором.
решение проблемы провалов в вариадики
не должно требовать его обязательного наличия.
0
02.08.2016, 22:10
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
02.08.2016, 22:10
Привет! Вот еще темы с ответами:

Определите класс матриц. Напишите перегруженные конструкторы для создания одномерной и двумерной матриц. В конструкторы передаются размерности матриц - Java SE
Доброго времени суток всем) Извините если не в том разделе) Не могу понять как решить данную задачу &quot;(Определите класс матриц. Напишите...

Variadic templates - C++
как можно сделать чтобы функция принимала бесконечное количество объектов std::string? (только их) я пробовал вот так: template&lt;const...

Variadic templates - C++
А как бы так исхитриться, чтобы шаблонная функция, использующая Variadic templates узнала размер передаваемых ей параметров с тем, чтобы,...

Variadic templates - C++
1. как можно сделать чтобы передавались ссылки на переменные? (с variadic templates) 2. как вообще обращаться к переменным объявленным...


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

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

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