Как отделить константную ссылку от значения?
От: ChainEG  
Дата: 29.10.09 16:04
Оценка:
Есть набор фубаров, унаследованных от общего абстрактного предка

class TFubar {};

class TFubarA: public TFubar {};
class TFubarB: public TFubar {};


Есть набор функций, помогающих получить нужный фубар. Причем некоторые их этих функций возвращают константную ссылку на уже существующий фубар, а некоторые возвращают новый фубар по значению.
TFubarA GetFubarA() { return TFubarA(); }
const TFubar& GetFubarRefB() { static TFubarB f; return f; }


Нужно сделать обертку для временного хранения фубара, типа такой:
template<typename FOOBAR>
class TFubarHolder {
  FOOBAR m_f;
 public:
  explicit TFubarHolder(FOOBAR f): m_f(f) {}
};


А еще хочется сделать функцию-хелпер для удобного создания временных оберток соответствующего типа. Что-то вроде этого:
template<typename FOOBAR>
TFubarHolder<FOOBAR> Hold(FOOBAR f)
{
  return TFubarHolder<FOOBAR>(f);
}


В общем, что-то наподобие std::pair и std::make_pair

Но сделать это нужно так, чтобы, если в функцию Hold() передать значение фубара, то обертка должна хранить значение фубара (копию).
А если в функцию Hold передать константную ссылку на фубар, то и обертка должна хранить константную ссылку на фубар.
Например:
Hold(GetFubarA());    // должно вернуть TFubarHolder<TFubarA>
Hold(GetFubarRefB()); // должно вернуть TFubarHolder<const TFubarB&> или TFubarHolder<const TFubar&>


Я пытался перегрузить функцию Hold(), чтобы одна вызывалась для ссылок, а другая — для значений. Не выходит — в обоих случаях перегрузка разрешается в пользу какой-то одной функции, а не разных. Либо перегрузка вообще не разрешается.

Есть ли какие-то решения? Есть ли решение, пригодное для MSVC6 (ограниченная поддержка шаблонов, в частности, отсутствие частичной специализации)?
Re: Как отделить константную ссылку от значения?
От: Кодт Россия  
Дата: 29.10.09 21:38
Оценка: 2 (1)
Здравствуйте, ChainEG, Вы писали:

Ниже много лирики, основная мысль в конце выделена жирным.


CEG>Я пытался перегрузить функцию Hold(), чтобы одна вызывалась для ссылок, а другая — для значений. Не выходит — в обоих случаях перегрузка разрешается в пользу какой-то одной функции, а не разных. Либо перегрузка вообще не разрешается.


Различать значения и ссылки очень, очень мучительно.
(Говорят, в C++0x есть rvalue references, но я в этом скорее осло, чем копенгаген).

Если ты имеешь дело с нульарными функциями, то можешь передавать не значение, а собственно функцию.
Выдрать тип из сигнатуры (T() или T const&()) — труда не составит.
Но это уж как-то совсем ограниченное решение.

CEG>Есть ли какие-то решения? Есть ли решение, пригодное для MSVC6 (ограниченная поддержка шаблонов, в частности, отсутствие частичной специализации)?


Мне кажется, правильно будет
1) Сперва задуматься — чем для тебя (в контексте твоей задачи) различаются ссылки и значения.
Ну, скажем, такое различие: значение, будучи временным объектом, живёт недолго; зато его можно копировать.

А так ли страшно, что значение живёт недолго?
Если сам Hold живёт в пределах полного выражения, то ссылка на временный объект вполне всех устроит.

А так ли страшно, что глобальную ссылку нельзя копировать?
Вообще, — страшно: полиморфизм, всё-таки. Но если функции, возвращающие глобальные ссылки, не апкастят тип, эта проблема снимается.
И ещё раз страшно, если эксплуатируется уникальность адреса либо копирование слишком дорого.

2) Постараться каким-то способом придти к единообразию.
Единообразие в виде константных ссылок — ограничено, в силу проблемы времени жизни. (Кстати, из-за этого я проклял библиотеку Локи).
Единообразие в виде значений — это либо беспощадное копирование, либо использование прокси-объектов.
Наконец, можно достичь единообразия в виде умных указателей (тот же boost::shared_ptr может и с глобальными неубиваемыми объектами иметь дело). Куча плюсов и минус в виде нагрузки на динамическую память.

Прокси могут быть
— настоящими, в иерархии твоих TFubar'ов
— резко отличными по типу: начиная с банальных указателей вместо ссылок, и кончая boost::cref и его аналогами

Выбор техники зависит от того, насколько мучительно будет переделать программу.
Грубо говоря, если const Fubar& foo() заменить на cref<Fubar> foo() или на Fubar* foo() — много ли клиентского кода отвалится?
Перекуём баги на фичи!
Re[2]: Как отделить константную ссылку от значения?
От: zaufi Земля  
Дата: 30.10.09 02:56
Оценка:
Здравствуйте, Кодт, Вы писали:


К>Различать значения и ссылки очень, очень мучительно.

К>(Говорят, в C++0x есть rvalue references, но я в этом скорее осло, чем копенгаген).

както в таком вот акцепте...

#include <iostream>
#include <string>
#include <typeinfo>
#include <cassert>
#include <malloc.h>
#include <cxxabi.h>
#include <boost/type_traits/is_reference.hpp>

using namespace std;

template <typename T>
std::string type_name(const T&)
{
    const char* const name = typeid(T).name();
    // Try to demangle name using gcc API (of couse if current compiler is gcc)
    int status;
    char* demangled_name = abi::__cxa_demangle(name, 0, 0, &status);
    if (!status)
    {
        std::string name = demangled_name;
        free(demangled_name);
        return name;
    }
    assert(!"How is that possible? Fail on demangle name produced by itself??");
    return name;
}

class TFubar {};

class TFubarA: public TFubar {};
class TFubarB: public TFubar {};

TFubarA GetFubarA() { return TFubarA(); }
const TFubar& GetFubarRefB() { static TFubarB f; return f; }

template<typename FOOBAR>
class TFubarHolder
{
  FOOBAR m_f;
 public:
  explicit TFubarHolder(FOOBAR f): m_f(f) {}
};

template <typename FOOBAR>
class TFubarHolder<const FOOBAR&>
{
  const FOOBAR& m_f;
 public:
  explicit TFubarHolder(const FOOBAR& f): m_f(f) {}
};

template <typename FOOBAR>
TFubarHolder<FOOBAR> Hold(FOOBAR&& f)
{
  return TFubarHolder<FOOBAR>(f);
}

int main()
{
    // должно вернуть TFubarHolder<TFubarA>
    auto f1 = Hold(GetFubarA());
    cout << type_name(f1) << endl;
    // должно вернуть TFubarHolder<const TFubarB&> или TFubarHolder<const TFubar&>
    auto f2 = Hold(GetFubarRefB());
    cout << type_name(f2) << endl;
    return 0;
}


zaufi /work/tests $ g++ -std=gnu++0x -o vr vr.cc
zaufi /work/tests $ ./vr
TFubarHolder<TFubarA>
TFubarHolder<TFubar const&>

zaufi /work/tests $ g++ --version
g++ (Gentoo 4.4.2 p1.0) 4.4.2
Copyright (C) 2009 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Re[2]: Как отделить константную ссылку от значения?
От: ChainEG  
Дата: 30.10.09 09:54
Оценка:
Здравствуйте, Кодт. Большое спасибо за столь развернутый ответ.
Вы писали:
К>Если ты имеешь дело с нульарными функциями, то можешь передавать не значение, а собственно функцию.
Идея мне понравилась. возьму на вооружение. Но в данном случае она не годится: функции-хелперы могут иметь произвольное количество аргументов. Кроме того, они могут быть как свободными функциями, так и членами классов.

К>Мне кажется, правильно будет

К>1) Сперва задуматься — чем для тебя (в контексте твоей задачи) различаются ссылки и значения.
К>Ну, скажем, такое различие: значение, будучи временным объектом, живёт недолго; зато его можно копировать.
К>А так ли страшно, что значение живёт недолго? Если сам Hold живёт в пределах полного выражения, то ссылка на временный объект вполне всех устроит.
Функцию Hold() предполагается использовать для создания временного Holder-а для передачи его в некую функцию. Примерно так:
RecognizeFubar(Hold(GetFubar()));


Что будет, если GetFubar() возвращает фубар по значению, а Hold() завернет его в TFubarHolder<const TFubar&>, как константную ссылку? Доживет ли этот временный фубар до возврата из функции RecognizeFubar(). Что-то мне подсказывает, что он имеет право сдохнуть сразу после выхода из конструктора TFubarHolder(), а в функцию RecognizeFubar() FubarHolder придет уже с невалидной ссылкой. Мне кажется, в этом случае надежнее заворачивать в TFubarHolder именно значение, а не ссылку. Хотя, может, я зря опасаюсь и применение здесь ссылок совершенно безопасно?

К>А так ли страшно, что глобальную ссылку нельзя копировать?

К> Вообще, — страшно: полиморфизм, всё-таки. Но если функции, возвращающие глобальные ссылки, не апкастят тип, эта проблема снимается.
Ссылки чаще всего будут ссылками на базовый класс TFubar, так что копировать их по значению нельзя. К счастью, TFubar является абстрактным классом, так что компилятор и не позволит их копировать, тем сасым не допустит срезки

К>2) Постараться каким-то способом придти к единообразию.

К>Выбор техники зависит от того, насколько мучительно будет переделать программу.
К>Грубо говоря, если const Fubar& foo() заменить на cref<Fubar> foo() или на Fubar* foo()- много ли клиентского кода отвалится?
Нет не много. Код пока живет только у меня и в небольшом количестве.

Я правильно понял идею, с прокси, предлагается сделать что-то вроде такого? :
template<typename FOOBAR>
TFubarHolder<FOOBAR> Hold(FOOBAR &f)
{
  return TFubarHolder<FOOBAR>(f); // возвращаем по значению
}

template<typename FOOBAR>
TFubarHolder<const FOOBAR&> Hold(reference_wrapper<const FOOBAR> &f)
{ 
  return TFubarHolder<const FOOBAR&>(f.get()); // возвращаем по ссылке
}

TFubarA GetFubarVal();
const TFubar& GetFubarRef();
reference_wrapper<const TFubar> GetFubarCRef();

void DevelopFubar(const TFubar &fubar)
{
  RecognizeFubar(Hold(fubar));          // a)
  RecognizeFubar(Hold(cref(fubar)));    // b) Безопасно, но нужно не забыть cref
  RecognizeFubar(Hold(GetFubarRef()));  // c) 
  RecognizeFubar(Hold(GetFubarCRef())); // d) Безопасно, передается ссылка
  RecognizeFubar(Hold(GetFubarVal()));  // e) Безопасно, скопируется по значению
}



Вызывают опасения случаи a) и с). Если я забуду поставить cref, то может произойти копирование по значению и может возникнуть срезка. Как бы сделать, чтобы при отсутствие cref гарантированно возникала ошибка компиляции?

И вернусь, к вопросу, заданному в начале этого поста.
Может, все-таки, сделать Hold() таким, чтобы он всегда заворачивал ссылку? Будет ли тогда безопасет случай e) ?
Re[3]: Как отделить константную ссылку от значения?
От: ChainEG  
Дата: 30.10.09 10:00
Оценка:
Здравствуйте, zaufi, Вы писали:

Z>

Z>template <typename FOOBAR>
Z>TFubarHolder<FOOBAR> Hold(FOOBAR&& f)
Z>{
Z>  return TFubarHolder<FOOBAR>(f);
Z>}
Z>

То есть один лишний амперсенд (rvalue-reference) полностью снимает проблему?

Z>
Z>    auto f1 = Hold(GetFubarA());
Z>

auto, наверное, тоже очень удобная штука.

Но мне пока приходится писать совместимо с MSVC 6.
Re[3]: Как отделить константную ссылку от значения?
От: Кодт Россия  
Дата: 30.10.09 10:25
Оценка:
Здравствуйте, ChainEG, Вы писали:

CEG>Функцию Hold() предполагается использовать для создания временного Holder-а для передачи его в некую функцию. Примерно так:

CEG>
CEG>RecognizeFubar(Hold(GetFubar()));
CEG>


CEG>Что будет, если GetFubar() возвращает фубар по значению, а Hold() завернет его в TFubarHolder<const TFubar&>, как константную ссылку? Доживет ли этот временный фубар до возврата из функции RecognizeFubar(). Что-то мне подсказывает, что он имеет право сдохнуть сразу после выхода из конструктора TFubarHolder(), а в функцию RecognizeFubar() FubarHolder придет уже с невалидной ссылкой. Мне кажется, в этом случае надежнее заворачивать в TFubarHolder именно значение, а не ссылку. Хотя, может, я зря опасаюсь и применение здесь ссылок совершенно безопасно?


Все временные объекты доживают до конца полного выражения. Так что здесь ссылки безопасны.

......

CEG>Я правильно понял идею, с прокси, предлагается сделать что-то вроде такого? :

...
Идея в том, чтобы Hold всегда держал копию значения. Но вот тип значения может быть как непосредственно TFubarxxx, так и reference_wrapper.
Перекуём баги на фичи!
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.