ternary для разных подклассов на стеке
От: tdiff  
Дата: 21.12.16 18:11
Оценка:
Привет

Хочется в зависимости от условия создавать на стеке объекты разных подтипов одного общего класса, но пока не получается:
class base { virtual void f() const = 0;};
class d1 : public base { void f() const override {...} };
class d2 : public base { void f() const override {...} };

const base& obj = true ? (const base&)d1() : d2();
obj.f();

Ошибка:
<source>:7:25: error: allocating an object of abstract class type 'const base'
const base& obj = true ? (const base&)d1() : d2();

Другими словами, я хочу, чтобы стек выглядел вот так:
[ссылка на объект ниже]
[d1 или d2]
и время жизни d1 или d2 совпадало с текущим скоупом.

Как-то можно этого добиться? Не хочется писать
if true
  d1().f();
else
  d2().f();

а создавать без повода объекты на хипе тоже как-то не круто.
Отредактировано 21.12.2016 18:13 tdiff . Предыдущая версия . Еще …
Отредактировано 21.12.2016 18:12 tdiff . Предыдущая версия .
Отредактировано 21.12.2016 18:11 tdiff . Предыдущая версия .
Re: ternary для разных подклассов на стеке
От: antropolog  
Дата: 21.12.16 18:21
Оценка: -1 :)
Здравствуйте, tdiff, Вы писали:

const base& obj = true ? (const base&)d1() : (const base&)d2();
Re: ternary для разных подклассов на стеке
От: Evgeny.Panasyuk Россия  
Дата: 21.12.16 18:30
Оценка: +2
Здравствуйте, tdiff, Вы писали:

T>Другими словами, я хочу, чтобы стек выглядел вот так:

T>[ссылка на объект ниже]
T>[d1 или d2]
T>и время жизни d1 или d2 совпадало с текущим скоупом.
T>...
T>а создавать без повода объекты на хипе тоже как-то не круто.

Boost.Variant
Re[2]: ternary для разных подклассов на стеке
От: Evgeny.Panasyuk Россия  
Дата: 21.12.16 18:36
Оценка: 9 (1) +2
Здравствуйте, antropolog, Вы писали:

A>
A>const base& obj = true ? (const base&)d1() : (const base&)d2();
A>


Здесь будет dangling reference.
Re: ternary для разных подклассов на стеке
От: Evgeny.Panasyuk Россия  
Дата: 21.12.16 18:45
Оценка:
Здравствуйте, tdiff, Вы писали:

T>Как-то можно этого добиться? Не хочется писать

T>
T>if true
T>  d1().f();
T>else
T>  d2().f();
T>


Как вариант можно вот так:
[&](base &&x)
{
    x.f();
}(true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));

Или вот так:
auto f = [&](auto &&x)
{
    x.f();
};
if(true) f(d1()); else f(d2());
Re[3]: ternary для разных подклассов на стеке
От: antropolog  
Дата: 21.12.16 19:11
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Здесь будет dangling reference.


да, спасибо, разобрался, проблема в самом касте, и ссылка инициализируется не rvalue а ссылкой
Отредактировано 21.12.2016 19:20 antropolog . Предыдущая версия .
Re[4]: ternary для разных подклассов на стеке
От: Evgeny.Panasyuk Россия  
Дата: 21.12.16 19:32
Оценка:
Здравствуйте, antropolog, Вы писали:

A>да, спасибо, разобрался, проблема в самом касте, и ссылка инициализируется не rvalue а ссылкой


Дело не в rvalue. Время жизни результата d1() — выражение, грубо говоря до точки с запятой ;. И только в исключительных случаях (специально оговоренных в стандарте) может быть продлено до конца блока, например
const base &x = d1();
Re[5]: ternary для разных подклассов на стеке
От: antropolog  
Дата: 21.12.16 19:43
Оценка: +1
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>const base &x = d1();

ну собственно меня и заинтересовало чем принципиально тернарный оператор здесь отличается, потому как например в случае с const base& x = true ? d1() : d1(); провисшей ссылки не будет.
Re[2]: ternary для разных подклассов на стеке
От: tdiff  
Дата: 22.12.16 10:07
Оценка:
Здравствуйте, Evgeny.Panasyuk, Вы писали:

EP>Как вариант можно вот так:

EP>
EP>[&](base &&x)
EP>{
EP>    x.f();
EP>}(true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));
EP>

Не совсем понимаю, в чём тут разница с
base && x = (true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));
x.f() ?
Re[3]: ternary для разных подклассов на стеке
От: N. I.  
Дата: 22.12.16 11:04
Оценка:
tdiff:

EP>>Как вариант можно вот так:

EP>>
EP>>[&](base &&x)
EP>>{
EP>>    x.f();
EP>>}(true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));
EP>>

T>Не совсем понимаю, в чём тут разница с
T>
T>base && x = (true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));
T>x.f() ?
T>

Разница в том, что во втором случае к моменту вызова f ссылка x указывает на мёртвый объект.

Я вот тут решил другой трюк провернуть

#include <iostream>

struct B
{
    virtual ~B() { std::cout << "~B()" << std::endl; }
    virtual void f() = 0;
};

struct D1 : B
{
    D1() { std::cout << "D1()" << std::endl; }
    ~D1() { std::cout << "~D1()" << std::endl; }
    void f() { std::cout << "D1::f" << std::endl; }
};

struct D2 : B
{
    D2() { std::cout << "D2()" << std::endl; }
    ~D2() { std::cout << "~D2()" << std::endl; }
    void f() { std::cout << "D2::f" << std::endl; }
};

int main()
{
    struct BRef
    {
        B &ref;
    };
    struct D1Wrapper
    {
        D1Wrapper() : value(), ref{value} {}
        D1 value;
        BRef ref;
    };
    struct D2Wrapper
    {
        D2Wrapper() : value(), ref{value} {}
        D2 value;
        BRef ref;
    };
    auto &&bref_1 = true ? D1Wrapper().ref : D2Wrapper().ref;
    auto &&bref_2 = false ? D1Wrapper().ref : D2Wrapper().ref;
    bref_1.ref.f();
    bref_2.ref.f();
}


и не поверил своим глазам: MSVC++ рвёт сразу G++ и Clang++ по части соответствия стандарту: первый компилирует пример корректно, другие два — нет. Кто б мог подумать, а?
Re[4]: ternary для разных подклассов на стеке
От: night beast СССР  
Дата: 22.12.16 11:14
Оценка:
Здравствуйте, N. I., Вы писали:

NI>Разница в том, что во втором случае к моменту вызова f ссылка x указывает на мёртвый объект.


NI>Я вот тут решил другой трюк провернуть


NI>
NI>int main()
NI>{
NI>    struct BRef
NI>    {
NI>        B &ref;
NI>    };
NI>    struct D1Wrapper
NI>    {
NI>        D1Wrapper() : value(), ref{value} {}
NI>        D1 value;
NI>        BRef ref;
NI>    };
NI>    struct D2Wrapper
NI>    {
NI>        D2Wrapper() : value(), ref{value} {}
NI>        D2 value;
NI>        BRef ref;
NI>    };
NI>    auto &&bref_1 = true ? D1Wrapper().ref : D2Wrapper().ref;
NI>    auto &&bref_2 = false ? D1Wrapper().ref : D2Wrapper().ref;
NI>    bref_1.ref.f();
NI>    bref_2.ref.f();
NI>}


NI>и не поверил своим глазам: MSVC++ рвёт сразу G++ и Clang++ по части соответствия стандарту: первый компилирует пример корректно, другие два — нет. Кто б мог подумать, а?


м... я так понимаю, очевидное предположение что bref_1.ref.f() -- это вызов функции удаленного объекта не соответствует действительности?
Отредактировано 22.12.2016 11:17 night beast . Предыдущая версия .
Re[5]: ternary для разных подклассов на стеке
От: N. I.  
Дата: 22.12.16 11:30
Оценка:
night beast:

NB>м... я так понимаю, очевидное предположение что bref_1.ref.f() -- это вызов функции удаленного объекта не соответствует действительности?


Я там несколько слукавил насчёт "стандарта" — C++17 всё-таки ещё не вышел По C++14 тернарный оператор может создать копию выбранного результата, и тогда да, bref_1.ref.f() может быть вызовом функции для удалённого объекта.
Re[6]: ternary для разных подклассов на стеке
От: night beast СССР  
Дата: 22.12.16 11:36
Оценка: +1
Здравствуйте, N. I., Вы писали:

NB>>м... я так понимаю, очевидное предположение что bref_1.ref.f() -- это вызов функции удаленного объекта не соответствует действительности?


NI>Я там несколько слукавил насчёт "стандарта" — C++17 всё-таки ещё не вышел По C++14 тернарный оператор может создать копию выбранного результата, и тогда да, bref_1.ref.f() может быть вызовом функции для удалённого объекта.


блин. как-так.
как D1Wrapper() может пережить конец выражения если напрямую нигде не используется?
Re[6]: ternary для разных подклассов на стеке
От: Кодт Россия  
Дата: 22.12.16 11:39
Оценка:
Здравствуйте, antropolog, Вы писали:

EP>>const base &x = d1();

A>ну собственно меня и заинтересовало чем принципиально тернарный оператор здесь отличается, потому как например в случае с const base& x = true ? d1() : d1(); провисшей ссылки не будет.

Здесь будет следующее:
— тип обеих веток одинаков, вопросов о приведении не будет
— тернарный оператор возвращает значение
— это значение попадает в скрытый объект типа d1
— ссылка продлевает время его жизни

А тебе нужна логика вот примерно такая
// продлеватели жизни на все случаи
std::optional<d1> tmp1;
std::optional<d2> tmp2;

// для любых выражений, возвращающих объекты по значению
const base& x = condition ? (const base&)(tmp1 = return_d1_byval()).value()
                          : (const base&)(tmp2 = return_d2_byval()).value() ;

// для конструкторов
const base& x = condition ? (tmp1.emplace(12,34), (const base&)tmp1.value())
                          : (tmp2.emplace("abc"), (const base&)tmp2.value()) ;


std::optional есть в C++17, а до того — есть boost::optional

Ну и на самом деле, два optional — это один variant.
Перекуём баги на фичи!
Re[7]: ternary для разных подклассов на стеке
От: night beast СССР  
Дата: 22.12.16 11:50
Оценка:
Здравствуйте, Кодт, Вы писали:

К>std::optional есть в C++17, а до того — есть boost::optional


раз пошла такая пьянка, то и по optional спрошу.
какие-нибудь ограничения на размер или хранимые значения в нем есть?
а то не очень бы хотелось чтобы он для int занимал в два раза больше памяти чем нужно...
Re[7]: ternary для разных подклассов на стеке
От: N. I.  
Дата: 22.12.16 12:30
Оценка: 12 (2)
night beast:

NB>блин. как-так.

NB>как D1Wrapper() может пережить конец выражения если напрямую нигде не используется?

Раньше было так: когда инициализируешь такую ссылку частью объекта (представленного prvalue выражением), то время жизни всего объекта целиком подлежит продлению:

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: [...]

В новой редакции это правило оставили, но, перечитав 5.2.5, я-таки осознал, что не понимаю, как данное правило можно применять теперь, если prvalue.x — это xvalue, а не prvalue, как было раньше (как компилятор узнает, что некое xvalue — это чей-то subobject?). Как минимум понятно, что guaranteed copy elision при получении результата тернарного оператора в том примере всё же быть не может, т.е. насчёт корректности примера я был не прав (похоже, без перечитывания всего стандарта выпендриваться с новыми фичами особо нельзя, стандартизаторы усё попоменяли, так не и знаешь, откуда ждать подвоха ).
Re[8]: ternary для разных подклассов на стеке
От: Кодт Россия  
Дата: 22.12.16 12:33
Оценка:
Здравствуйте, night beast, Вы писали:

NB>раз пошла такая пьянка, то и по optional спрошу.

NB>какие-нибудь ограничения на размер или хранимые значения в нем есть?
NB>а то не очень бы хотелось чтобы он для int занимал в два раза больше памяти чем нужно...

Ну а куда ты вынесешь один бит признака "есть данные — нет данных"?
Экономить можно только в том случае, когда объект optional не независимый, а в составе, ну хотя бы, вектора.
template<class T, size_t N>
class optional_array {
  bitset<N> present;

  using memory = aligned_storage<sizeof(T), alignof(T)>::type;
  array<memory, N> data;

  const T& peek(size_t i) const {
    return reinterpret_cast<const T&>(data[i]);
  }
  T& peek(size_t i) {
    return reinterpret_cast<const T&>(data[i]);
  }
public:
  T* get(size_t i) {
    assert(i < N);
    return present[i] ? &peek(i) : nullptr;
  }
  void set(size_t i, const T& v) {  // и то же самое для T&& и для emplace.
    if (present[i])
      peek(i) = v;
    else {
      new(&peek(i)) T(v);
      present[i] = true;
    }
  }
  void reset(size_t i) {
    if (present[i]) {
      peek(i).~T();
      present[i] = false;
    }
  }

  ~optional_array() {
    for(size_t i = 0; i < N; ++i) reset(i);
  }
  optional_array(const optional_array& arr) {  // и то же самое для &&
    for(size_t i = 0; i < N; ++i) if(arr.get(i)) set(arr.get(i));
  }
};
Перекуём баги на фичи!
Re[8]: ternary для разных подклассов на стеке
От: night beast СССР  
Дата: 22.12.16 12:40
Оценка:
Здравствуйте, N. I., Вы писали:

NB>>блин. как-так.

NB>>как D1Wrapper() может пережить конец выражения если напрямую нигде не используется?

NI>Раньше было так: когда инициализируешь такую ссылку частью объекта (представленного prvalue выражением), то время жизни всего объекта целиком подлежит продлению:

NI>

The temporary to which the reference is bound or the temporary that is the complete object of a subobject to which the reference is bound persists for the lifetime of the reference except: [...]



спасиб. наверно, пока такие трюки опасно применять...
Re[9]: ternary для разных подклассов на стеке
От: night beast СССР  
Дата: 22.12.16 12:45
Оценка:
Здравствуйте, Кодт, Вы писали:

NB>>раз пошла такая пьянка, то и по optional спрошу.

NB>>какие-нибудь ограничения на размер или хранимые значения в нем есть?
NB>>а то не очень бы хотелось чтобы он для int занимал в два раза больше памяти чем нужно...

К>Ну а куда ты вынесешь один бит признака "есть данные — нет данных"?


у себя в велосипеде я использую особые значения вроде INT_MIN (конкретная стратегия применят флаг или некоторое значение задается в треитсах)
Отредактировано 22.12.2016 12:54 night beast . Предыдущая версия .
Re[4]: ternary для разных подклассов на стеке
От: tdiff  
Дата: 22.12.16 13:01
Оценка:
Здравствуйте, N. I., Вы писали:

T>>Не совсем понимаю, в чём тут разница с

T>>
T>>base && x = (true ? static_cast<base&&>(d1()) : static_cast<base&&>(d2()));
T>>x.f() ?
T>>


NI>Разница в том, что во втором случае к моменту вызова f ссылка x указывает на мёртвый объект.


Почему, если base&& должна продлевать время жизни temporary?

У меня есть догадка, что происходит что-то вроде:
1. создаётся temporary d1() с типом d1&&
2. тип этого объекта конвертируется в base&&
3. результатом ternary является base&&, которая продлевает время жизни d1
3. этой ссылкой инициализируется ссылка base&& x, которая почему-то уже не продлевает жизни d1

Это, в частности, подтверждается cppref:

In general, the lifetime of a temporary cannot be further extended by "passing it on": a second reference, initialized from the reference to which the temporary was bound, does not affect its lifetime.

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