оператор << перегрузка для умных указателей - PullRequest
3 голосов
/ 02 января 2012

Я хотел бы перегрузить оператор <<, чтобы он мог работать с <code>shared_ptr.

template<typename T>
struct foo
{
    virtual foo& operator<<(const T& e) = 0;
};

foo<int> f1;
f1 << 1;

std::shared_ptr<foo<int>> f2(new foo<int>());
f2 << 1;

Моя первая попытка заключается в следующем, но проблема в том, что он также включает поведение длялюбой класс.

template<typename T, typename U>
const std::shared_ptr<T>& operator<<(const std::shared_ptr<T>& o, const U& e)
{
    *o << e;
    return o;
}

Моя вторая попытка следующая:

template<typename T, typename U>
const std::shared_ptr<foo<T>>& operator<<(const std::shared_ptr<foo<T>>& o, const U& e)
{
    *o << e;
    return o;
}

Проблема с этим решением не работает для типов, наследующих foo, поскольку T не может быть автоматически выведен.

Таким образом, я мог бы пропустить U и использовать вместо него T, и в этом случае T будет выведен из второго аргумента, а аргумент для o можно преобразовать в foo<T>.

template<typename T, typename U>
const std::shared_ptr<foo<T>>& operator<<(const std::shared_ptr<foo<T>>& o, const T& e)
{
    *o << e;
    return o;
}

Но тогда не сработает следующее:

struct c    
{    
};

struct a
{
    a();
    a(c); // implicit conversion
};

struct b
{
    operator a(); // implicit conversion
};

auto f = std::make_shared<foo<a>>();
f << c; // doesn't work.
f << b; // doesn't work.

Есть идеи, как сделать рабочее решение?

Ответы [ 3 ]

3 голосов
/ 02 января 2012

Некоторые параметры, см. Вторую трансляцию в https://ideone.com/26nqr

Задано

#include <iostream>
#include <memory>
using namespace std;

template<typename T> struct foo {
    virtual foo& operator<<(const T& e) const { std::cout << "check foo\n"; }
};

//////
// derived instances

struct derived : foo<int> {
    virtual derived& operator<<(const int& e) const { std::cout << "check derived\n"; }
};

template<typename T> struct genericDerived : foo<T> {
    virtual derived& operator<<(const T& e) const { std::cout << "check genericDerived\n"; }
};

Простой: аргументы шаблона шаблона

template<typename T, typename U, template <typename> class X>
const std::shared_ptr<X<T>>& operator<<(const std::shared_ptr<X<T>>& o, const U& e)
{
    *o << e;
    return o;
}

int main()
{
    auto f = make_shared<foo<int>>();
    f << 1;

    auto d = make_shared<derived>();
    d << 2; // compile error

    auto g = make_shared<genericDerived<int>>();
    g << 3; // SUCCESS!
}

Завершитель: SFINAE

Выше не учитываются производные классы (случай 2).Для этого я прибегну к

#include <type_traits>
namespace detail
{
    template<typename Foo, typename T>
        const std::shared_ptr<Foo>& dispatch_lshift(
                  const std::shared_ptr<Foo>& o, const T& e, 
                  const std::true_type& enabler)
        {
            *o << e;
            return o;
        }
}

template<typename Foo, typename T>
    const std::shared_ptr<Foo>& operator<<(const std::shared_ptr<Foo>& o, const T& e)
{
    return detail::dispatch_lshift(o, e, std::is_convertible<Foo*, foo<T>* >());
}

int main()
{
    auto f = make_shared<foo<int>>();
    f << 1;

    auto d = make_shared<derived>();
    d << 2;

    auto g = make_shared<genericDerived<int>>();
    g << 3;

    auto x = make_shared<int>();
    // x << 4; // correctly FAILS to compile    
}
1 голос
/ 02 января 2012

Может быть, вы ставите себя на плохую арену , заставляющую C ++ выглядеть как Java.

Указатели, значения и ссылки C ++ имеют разные типы и имеют разную семантикуи операции.В общем случае не стоит смешивать их в один и тот же синтаксис.Указатели не являются их значениями: они могут указывать на типы, отличные от того, на который они ссылаются (типично в случае наследования).Поэтому в общем случае лучше, чтобы разыменование указателя оставалось явным.

Тем не менее, сохранение в потоке «значения указателя» (а не «pointe * d * value») не представляет интереса, поскольку это значение (адрес памяти) будет бессмысленным при потоковом чтении.

Если вы хотите правильно «сохранить то, что указано», при сохранении указателя вы должны сохранить что-то, говорящее «какой тип объекта вы»«сохранить», а затем полиморфно сохранить объект (посредством вызова виртуальной функции); o также сохранить производные данные.Между тем, вам также необходимо отслеживать циклические ссылки, чтобы не сохранять один и тот же объект несколько раз (и загружать его обратно как отдельные объекты), поэтому также требуется «идентичность».

Чтобы загрузить объект обратноВы должны сначала прочитать метаданные, которые вы сохранили, чтобы описать тип, и на основе этого создать соответствующий объект, а затем переназначить его члены данным, загруженным при извлечении.(Или, если была сохранена только личность, наведите указатель на уже воссозданный соответствующий объект).

Все эти материалы идут под именем serialization .Существует множество решений, вам, вероятно, следует использовать Google для этого ключа.

0 голосов
/ 02 января 2012

Вы можете использовать свое первое решение с enable_if (я использую пространства имен C ++ 03):

template <typename T>
class is_funky : public boost::false_type {};

template <typename S>
class is_funky< Foo<S> > : public boost::true_type {};

template<typename T, typename U>
typename boost::enable_if< is_funky<T>, const boost::shared_ptr<T>& >::type 
operator<<(const boost::shared_ptr<T>& o, const U& e)
{
    *o << e;
    return o;
}

Возможно, вы захотите найти более подходящее имя, чем is_funky в рабочем коде, но это зависит от контекста.

...