2

I've extended QDataStream with a template conversion operator so that the datastream loads from itself and converts to any supported type, as such:

class ConvertibleQDataStream : public QDataStream
{
public:

    using QDataStream::QDataStream;

    template <class T>
    explicit operator T ()
    {
        T t;
        *this >> t;
        return t;
    }
};

And one can add support to types not supported by QDataStream by overloading operator >>, as such:

template <class T>
ConvertibleQDataStream&
operator >> (ConvertibleQDataStream& ds, std::vector<T>& v)
{
    //Called for std::vector's.
    return ds;
}

The idea is to be able to construct non-default constructible classes directly from a stream, like this:

class Bar
{
public:

    Bar(ConvertibleQDataStream&);
};

class Foo
{
    int mInt;
    std::vector<double> mVector;
    Bar mBar;

public:

    Foo(ConvertibleQDataStream& ds) :
        mInt(ds),     //Calls operator >> for int and converts to int 
        mVector(ds),  //Calls operator >> for std::vector<T> and converts to std::vector<T>
        mBar(ds)      //Plain constructor call 
    {}
};

This works great except when a member is a std::optional. std::optional's forwarding constructor is called in stead of ConvertibleQDataStream's template conversion operator:

template <class T>
ConvertibleQDataStream&
operator >> (ConvertibleQDataStream& ds, std::optional<T>& o)
{
    //Never called :(
    return ds;
}

class Qux
{
    std::optional<Bar> mOptional;

public:

    Foo(ConvertibleQDataStream& ds) :
        mOptional(ds) //calls Bar::Bar(ConvertibleQDataStream&) rather then operator >> for std::optional<T> due to forwarding c'tor.
    {}
};

Can one disable std::optional's forwarding constructor? Or another workaround for this.

Unimportant
  • 2,076
  • 14
  • 19
  • What's wrong with `Foo(ConvertibleQDataStream& ds){ ds >> mOptional; }`? – alter_igel May 24 '19 at 19:47
  • @alterigel: A class might have non-default constructible members. These have to be initialised in the member-initialiser list. As the order in which data is extracted from the stream is important some things cannot be moved to the constructor body. – Unimportant May 24 '19 at 19:53
  • @Unimportant do you mean that you want `ConvertibleQDataStream::operator T()` to be called with `T=std::optional`? – alter_igel May 24 '19 at 19:58
  • @alterigel Yes, it works for all other types but forwarding constructors get in the way. – Unimportant May 24 '19 at 20:01
  • standard conversion got preference over the user-defined. – Swift - Friday Pie May 24 '19 at 20:07

1 Answers1

3

This isn't a problem with option, this is a problem in your design where mOptional is constructable from ConvertibleQDataStream.

C++ conversion rules can be a nightmare and should likely be avoided in this case by providing explicit get operators.

class ConvertibleQDataStream : public QDataStream
{
public:
    using QDataStream::QDataStream;

    template <class T>
     T Get() const
    {
        T t;
        *this >> t;
        return t;
    }
};

class Qux
{
    std::optional<Bar> mOptional;

public:

    Foo(ConvertibleQDataStream& ds) :
        mOptional(ds.Get<std::optional<Bar>>())
    {}
};
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • They aren't that much of nightmare, allowing user-defined conversion to be preferred would cause `#define true false` kind of chaos. – Swift - Friday Pie May 24 '19 at 20:08
  • With perfect forwarding constructors they cause all sorts of crazyness, even in the stl. std::optional behaves particularly badly: std::optional x; // empty std::optional y(x); // what do you think happens here? – IdeaHat May 24 '19 at 20:19
  • not sure what do you mean. they are both empty... because x was empty? Doesn't it use a copy-constructor? Other constructors are explicit or conditionally explicit. – Swift - Friday Pie May 24 '19 at 20:39
  • @Swift-FridayPie y actually constains false. std::optionl is implicitly convertable to bool. – IdeaHat Mar 18 '20 at 16:35