Универсальный конструктор
От: B0FEE664  
Дата: 07.07.16 13:03
Оценка:
В рамках продолжения изучения C++11.

Правильно ли я понимаю, что в производных классах теперь не нужно дублировать конструкторы базового класса, а можно написать один Универсальный Конструктор:

template<class TData, class... Args>
    Derived(TData&& rrDerivedData, Args&&... args)
      : Base     (std::forward<Args >(args)...),
        m_strData(std::forward<TData>(rrDerivedData))
    {
    }


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

Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными:
    template<class... Args>
    Data(std::string&& rrData, Args&&... args)
      : Base     (std::forward<Args >(args  )...),
        m_strData(std::move          (rrData)   )
    {
    }



  Скрытый текст
#include <iostream>
#include <stdlib.h> 
#include <string>


class Base
{
  public:
    Base()
      : m_n(0)
    {
    }

    Base(int n, const std::string& strName, const std::string& strId)
      : m_n      (n      ),
        m_strName(strName),
        m_strId  (strId  )
    {
    }
    
    Base(const std::string& strName, const std::string& strId)
      : m_n      (0      ),
        m_strName(strName),
        m_strId  (strId  )
    {
    }

  public:
    int         m_n;
    std::string m_strName;
    std::string m_strId;
};


class Data : public Base
{
  public:
    Data()
    {
    }


    template<class TData, class... Args>
    Data(TData&& rrData, Args&&... args)
      : Base     (std::forward<Args >(args  )...),
        m_strData(std::forward<TData>(rrData)   )
    {
    }

  public:
    std::string m_strData; // before constructor
  public:
    template<class... Args>
    Data(decltype(m_strData)&& rrData, Args&&... args)
      : Base     (std::forward<Args >(args  )...),
        m_strData(std::move          (rrData)   )
    {
    }
    
};
     


int main(int argc, char* argv[])
{
    Data d1("data");
    Data d2("data", 1, "name", "id");
    Data d3("data", "name", "id");

    Base b;
    Data d4("data", b);

    Base b2(2, "b2", "test");
    Data d5("data", b2);

    Base b3(2, "b2", "test");
    Data d6("data", std::move(b3));
    std::cout << "b3.m_strName = " << b3.m_strName << std::endl;

    Data d7("data", d6);
    Data d8(d7);

    std::cout << "\n                            the end" << std::endl;
    return 0;
}



PS При множественном наследовании есть простой способ написать универсальный конструктор?
И каждый день — без права на ошибку...
Отредактировано 07.07.2016 13:52 B0FEE664 . Предыдущая версия . Еще …
Отредактировано 07.07.2016 13:50 B0FEE664 . Предыдущая версия .
Re: Универсальный конструктор
От: watchmaker  
Дата: 07.07.16 14:03
Оценка: 1 (1)
Здравствуйте, B0FEE664, Вы писали:

BFE> Или в таком подходе есть скрытые проблемы?

Самая главная тут проблема — не видно а что конкретно может скрываться за …args. Глядя на конструктор Data я не могу сказать, какие агрументы и каких типов мне можно или нужно передать даже чтобы код просто скомпилировался — для этого нужно изучать дополнительно ещё и конструкторы Base.
Для всяких утилитарных внутренних классов-прослоек (и прочего write-only кода) так ещё можно делать, а для внешнего интерфейса — не стоит.
Но если забыть об абсолютной нечитаемости кода, то в остальном подход всем хорош :)

BFE>PS При множественном наследовании есть простой способ написать универсальный конструктор?


Чтобы можно было передать несколько списков параметров в разные базовые конструкторы? В STL это сделано через std::piecewise_construct — не очень удобный механизм, но, к счастью, и нужен он редко.
Re: Универсальный конструктор
От: _hum_ Беларусь  
Дата: 07.07.16 14:13
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>В рамках продолжения изучения C++11.


BFE>Правильно ли я понимаю, что в производных классах теперь не нужно дублировать конструкторы базового класса, а можно написать один Универсальный Конструктор

BFE>который первыми аргументами принимает параметры для своих данных, а параметры для базового класса передаются прямой передачей в виде вариадик хвоста? Или в таком подходе есть скрытые проблемы?

BFE>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными


ага, а еще "Avoid Overloading on Universal References"

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

п.с. ссылку теперь называют не универсальной, а forwarding reference
Re[2]: Универсальный конструктор
От: B0FEE664  
Дата: 07.07.16 18:31
Оценка:
Здравствуйте, _hum_, Вы писали:

BFE>>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными

__>ага, а еще "Avoid Overloading on Universal References"
И там же приводятся решения проблемы.

__>имхо, чем так постоянно опасаться, что что-то лишнее пролезет, так лучше уж напрямую использовать Inheriting constructors, хотя и там надо грабли обходить..

Это уже С++14, но надо будет посмотреть, что они могут.

__>п.с. ссылку теперь называют не универсальной, а forwarding reference

Ну и как это будет на русском? Опережающая ссылка? Короче, на русский это не переводится.
Это пока. Потом они ещё что-нибудь поменяют и переименуют...
Впрочем... Они нормального определения ни для rvalue и ни для lvalue дать не могут, а уже на их основе производные понятия выводят!

Вот смотрите, Herb Sutter пишет:

A reference type that is declared using & is called an lvalue reference, and a reference type that is declared using && is called an rvalue reference or a forwarding reference.
Lvalue references and rvalue references and forwarding references are distinct types. Except where explicitly noted, they are semantically equivalent and commonly referred to as references.


Вообще-то универсальная ссылка это ни один из перечисленных типов, но то, что их все объединяет в единой сущности.

И вообще, похоже Sutter сам себе противоречит:

template<class Y>
void bar( Y&& y );


bar takes an lvalue or rvalue reference to everything: const, volatile, both, and neither.*
bar accepts all Y objects.
bar’s parameter is for forwarding its argument onward.


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

a reference that can be used everywhere; or
a reference that can be used for everything; or
something similar


Ну и? Ссылка ссылается на любые типы, используется для любых объектов и форвардит аргументы (куда) подальше. Разумеется это универсальная ссылка. Другое дело, что не всякий тип с двумя арсенидами является универсальной ссылкой и это надо понимать. Понимает ли это Sutter — я . Вполне допускаю, что это я чего-то не понимаю, но его аргументация выглядит подозрительно.
И каждый день — без права на ошибку...
Re[3]: Универсальный конструктор
От: _hum_ Беларусь  
Дата: 08.07.16 08:39
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, _hum_, Вы писали:


BFE>>>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными

__>>ага, а еще "Avoid Overloading on Universal References"
BFE>И там же приводятся решения проблемы.

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

__>>имхо, чем так постоянно опасаться, что что-то лишнее пролезет, так лучше уж напрямую использовать Inheriting constructors, хотя и там надо грабли обходить..

BFE>Это уже С++14, но надо будет посмотреть, что они могут.

вроде, не все с++14. базовые вещи, наподобие
  code

struct B1 {
    B1(int){};
};
struct D1 : B1 {
    using B1::B1;
};
 


int main()
{
  D1 X(1);
}

появились уже в с++11


__>>п.с. ссылку теперь называют не универсальной, а forwarding reference

BFE>Ну и как это будет на русском? Опережающая ссылка? Короче, на русский это не переводится.

"продвигающая", "пересылающая", "форвардирующая"

BFE>Это пока. Потом они ещё что-нибудь поменяют и переименуют...


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

BFE>Впрочем... Они нормального определения ни для rvalue и ни для lvalue дать не могут, а уже на их основе производные понятия выводят!


да, у меня тоже такое ощущение, что у них проблема с определением понятий — получается очень криво и неоднозначно. к тому же дают еще и кривые имена (один std::move чего стоит).

BFE>Вот смотрите, Herb Sutter пишет:


BFE>

A reference type that is declared using & is called an lvalue reference, and a reference type that is declared using && is called an rvalue reference or a forwarding reference.
BFE>Lvalue references and rvalue references and forwarding references are distinct types. Except where explicitly noted, they are semantically equivalent and commonly referred to as references.



BFE>Вообще-то универсальная ссылка это ни один из перечисленных типов, но то, что их все объединяет в единой сущности.


так там же говорится, что это определение некорректное:

This statement about && is either untrue or misleading, at least insofar as the feature is used; for example, this states normatively that bar’s parameter is an rvalue reference. We think this illustrates the confusion surrounding the && punning.


BFE>И вообще, похоже Sutter сам себе противоречит:


BFE>

BFE>

BFE>template<class Y>
BFE>void bar( Y&& y );
BFE>


BFE>bar takes an lvalue or rvalue reference to everything: const, volatile, both, and neither.*
BFE>bar accepts all Y objects.
BFE>bar’s parameter is for forwarding its argument onward.



BFE>А потом пишет, что чтобы называться универсальной ссылка должна быть:

BFE>

BFE>a reference that can be used everywhere; or
BFE>a reference that can be used for everything; or
BFE>something similar


BFE>Ну и? Ссылка ссылается на любые типы, используется для любых объектов и форвардит аргументы (куда) подальше. Разумеется это универсальная ссылка. Другое дело, что не всякий тип с двумя арсенидами является универсальной ссылкой и это надо понимать. Понимает ли это Sutter — я . Вполне допускаю, что это я чего-то не понимаю, но его аргументация выглядит подозрительно.


нет, там смысл в другом — что это конструкция сама по себе не определяет тип — объектом, у которого такой "тип" непонятно как пользоваться ("directly use it would be madness"), и смысл такой конструкции просто "перефарвордить" объект тому, кто уже знает, что с ним делать:

In the body of bar, the parameter y is sometimes const, and sometimes volatile, and sometimes both, and sometimes nei-ther. If bar were actually going to directly use its parameter, this would be madness—it would care whether its parameter, which to the function is a local variable, was const or not, volatile or not, and so on. But it doesn’t care, because its purpose is not to use the parameter directly, but to be “neutral passthrough” code that is agnostic to what it’s being given and faithfully pass it along to other code that will use the parameter and may care about the argument’s const-ness, volatile-less, and rvalue-ness.


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

So the rub is that it is a name that just rolls off the tongue and is misleading because “universal references” aren’t universal in the sense of how pervasively they should be used. Furthermore, they aren’t even really references per se, but rather a set of rules for using references in a particular way in a particular context with some language support for that use, and that use is forwarding.


п.с. кстати, они же сами признают, что это не настоящая ссылка, почему же тогда не используют термин "метассылка" (чтобы подчеркнуть ее уровень абстракции).
Re[4]: Универсальный конструктор
От: B0FEE664  
Дата: 08.07.16 12:07
Оценка:
Здравствуйте, _hum_, Вы писали:

BFE>>>>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными

__>>>ага, а еще "Avoid Overloading on Universal References"
BFE>>И там же приводятся решения проблемы.
__>да, только его многословность практически сводит на нет выгоду от использования "универсального конструктора"

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

Предположим, что у класса A есть два конструктора: A(std::string, int) и A(int, std::string, int).
И пусть у класса B : public A есть своё поле int, которое тоже надо инициализировать:
class B : public A
{
  public:
    template<class... Args>
    B(int nN, Args&&... args)
      : A   (std::forward<Args >(args  )...),
        m_nN(nN                            )
    {
    }
  private:
    int m_nN;
};


Соответственно есть куча мест, где B используется. Рассмотрим вызов конструктора:
  B b(1,2,"asdf",4);

Здесь поле B::m_nN будет инициализировано единицей, а параметры 2,"asdf" и 4 попадут в конструктор A.
Видите проблему? Нет? Правильно. Проблема возникает если мы добавим ещё одно поле int m_nM в класс B и соответственно перепишем конструктор:
class B : public A
{
  public:
    template<class... Args>
    B(int nN, int nM, Args&&... args)
      : A   (std::forward<Args >(args  )...),
        m_nN(nN                            ),
        m_nM(nM                            )
    {
    }
  private:
    int m_nN;
    int m_nM;
};

После такого изменения код скомпилируется без ошибок и даже запустится, но вести будет странно, ведь теперь при всех не изменённых вызовах
  B b(1,2,"asdf",4);

B::m_nN будет 1, m_nM будет 2, а в конструктор A уйдут два параметра: "asdf" и 4. Выявить все места конструирования объектов типа B и исправить параметры не будет простой задачей исправления ошибок компиляции.
И каждый день — без права на ошибку...
Re: Универсальный конструктор
От: Кодт Россия  
Дата: 08.07.16 16:22
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования.


Там засада таилась ещё до 11 года.
struct Base {
  Base(Foo);
  Base(Xyz);
  Base(const Base&); // cctor - пользовательский или определённый компилятором
};

struct Derived : Base {
  template<class T> Derived(const T& arg) : Base(arg) { ... }
};

Foo f;
Xyz x;

Base b;
Derived d1(f), d2(x),
        d3(b); // ой! получили срезку


Вообще, место тонкое и странное.
http://ideone.com/HCKwKm
struct foo {};
struct xyz {};

struct base {
    base(foo) { cout << "base(foo)\n"; }
    base(xyz) { cout << "base(xyz)\n"; }
    base(const base&) { cout << "base(base) - cctor\n"; }
};

struct derived : base {
    // (1)
    template<class... Args>
    derived(Args&&... args) : base(forward<Args>(args)...) {
        cout << __PRETTY_FUNCTION__ << endl;
    }

    // (2)
    template<class Arg>
    derived(Arg&& arg) : base(arg) {
        cout << __PRETTY_FUNCTION__ << endl;
    }

    // (3)
    template<class Arg>
    derived(const Arg& arg) : base(arg) {
        cout << __PRETTY_FUNCTION__ << endl;
    }

    derived(const derived& d) : base(d) { cout << "derived cctor\n"; }

    derived(derived&& d) : base(d) { cout << "derived mctor\n"; }
    
    derived(xyz x) : base(x) { cout << "derived(xyz)\n"; }
};

int main() {
    derived d1((foo())); // шаблонный
    cout << "-----" << endl;
    derived d2((xyz())); // конкретный
    cout << "-----" << endl;
    derived d3(d1);      // возможны варианты!!!
    cout << "-----" << endl;
    return 0;
}

Если оставить только (3) — то сработает конструктор копирования.
Если добавить (1) или (2) — будет отдано предпочтение шаблонному конструктору (а если добавить оба — то (2)).
Вот это я не понял! Всегда же было — предпочтение конкретной функции. И ладно бы там ссылка такая — ссылка сякая, но ведь объявлены оба конкретных конструктора — копирования и перемещения.
Перекуём баги на фичи!
Re[2]: Универсальный конструктор
От: B0FEE664  
Дата: 08.07.16 17:06
Оценка: 68 (1)
Здравствуйте, Кодт, Вы писали:

К>Вообще, место тонкое и странное.

К>http://ideone.com/HCKwKm
К>
К>struct foo {};
К>struct xyz {};

К>struct base {
К>    base(foo) { cout << "base(foo)\n"; }
К>    base(xyz) { cout << "base(xyz)\n"; }
К>    base(const base&) { cout << "base(base) - cctor\n"; }
К>};

К>struct derived : base {
К>    // (1)
К>    template<class... Args>
К>    derived(Args&&... args) : base(forward<Args>(args)...) {
К>        cout << __PRETTY_FUNCTION__ << endl;
К>    }

К>    // (2)
К>    template<class Arg>
К>    derived(Arg&& arg) : base(arg) {
К>        cout << __PRETTY_FUNCTION__ << endl;
К>    }

К>    // (3)
К>    template<class Arg>
К>    derived(const Arg& arg) : base(arg) {
К>        cout << __PRETTY_FUNCTION__ << endl;
К>    }

К>    derived(const derived& d) : base(d) { cout << "derived cctor\n"; }

К>    derived(derived&& d) : base(d) { cout << "derived mctor\n"; }
    
К>    derived(xyz x) : base(x) { cout << "derived(xyz)\n"; }
К>};

К>int main() {
К>    derived d1((foo())); // шаблонный
К>    cout << "-----" << endl;
К>    derived d2((xyz())); // конкретный
К>    cout << "-----" << endl;
К>    derived d3(d1);      // возможны варианты!!!
К>    cout << "-----" << endl;
К>    return 0;
К>}
К>

К>Если оставить только (3) — то сработает конструктор копирования.
К>Если добавить (1) или (2) — будет отдано предпочтение шаблонному конструктору (а если добавить оба — то (2)).
К>Вот это я не понял! Всегда же было — предпочтение конкретной функции. И ладно бы там ссылка такая — ссылка сякая, но ведь объявлены оба конкретных конструктора — копирования и перемещения.

На это все попадаются. Дело в том, что при подстановке в (2) после инстанцирования имеем выбор из:

derived(derived& arg)
derived(const derived& d)
derived(derived&& arg)

для derived d1 и вызова derived d3(d1) выбирается первый вариант.
если написать const derived d1 то будет второй
И каждый день — без права на ошибку...
Re[3]: Универсальный конструктор
От: Кодт Россия  
Дата: 08.07.16 17:20
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>На это все попадаются. Дело в том, что при подстановке в (2) после инстанцирования имеем выбор из:

BFE>derived(derived& arg)

Ай блииин, туп-туп. Шаблон в роли копирующего неконстантного конструктора. Боль и униженьице.
Перекуём баги на фичи!
Re[3]: Универсальный конструктор
От: Vain Россия google.ru
Дата: 09.07.16 12:45
Оценка: 1 (1) +1
Здравствуйте, B0FEE664, Вы писали:

BFE>На это все попадаются.

Ужас какой-то, нахрена оно вообще тогда нужно? До этого язык был хоть с недоделками, но относительно простой, а теперь с недоделками и чёрт ногу сломает!
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re[5]: Универсальный конструктор
От: _hum_ Беларусь  
Дата: 09.07.16 15:37
Оценка:
Здравствуйте, B0FEE664, Вы писали:

BFE>Здравствуйте, _hum_, Вы писали:


BFE>>>>>Update: кажется я нашёл одну: этот конструктор настолько универсальный, что перекрывает конструктор копирования. Значит аргументы самого класса Derived не должны быть шаблонными

__>>>>ага, а еще "Avoid Overloading on Universal References"
BFE>>>И там же приводятся решения проблемы.
__>>да, только его многословность практически сводит на нет выгоду от использования "универсального конструктора"

BFE>Я думаю, что важнее тут не многословность, а та проблема, на которую сослался watchmaker: мы не видим что и куда передаётся. Следствием этого будет, например, неуловимая ошибка в следующей ситуации:


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

Scott’s advice is to instead write one function, the one taking the universal reference, and internally dispatch to one of two helpers. One sensible way to dispatch might be to use the std::is_lvalue_reference trait, like so:



template<typename T>
void foo_impl( T && t, std::true_type )
  {/* LVALUES HERE */}

template<typename T>
void foo_impl( T && t, std::false_type )
  {/* RVALUES HERE */}

template<typename T>
void foo( T && t )
{
    foo_impl( std::forward<T>(t),
              std::is_lvalue_reference<T>() );
}



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