2

Background:
Our software uses multiple APIs to do file i/o: FILE*, CStdio (and several derivatives), HANDLE, ...

I wrote a FilePointer RAII wrapper for FILE*, which has served us well as a drop-in replacement for all of the existing C code.

Newer code generally used a CStdio-derived or wrappered class.

Recently, I've written a SimpleTextFile to handle UTF-16LE i/o, in addition to MBCS of our prior versions.

The interfaces for these various classes are similar, but not identical. I thought I could write some utility algorithms using policy template classes to adapt the utility algorithms to the various file types. This has been somewhat successful, however, I often have the need to mixin a line-reader-filter of some sort, often within the utility algorithms.

And here's where the rub comes in - if I've mixed in a line-reader-with-filter, any algorithms that this is passed to can no longer use policy classes to figure out how to adapt to the underlying type (because R is now a Wrapper<R>, and no policy exists for a Wrapper<R>).

Question:
How might I make mixin template classes that can provide new behavior to an existing type while still allowing various polices that worked on the underlying type to continue to work?

Details:
policy template:
StreamPositionPolicy<T> - provides GetPosition() and SetPosition() adapted to a T. LineReaderPolicy<T> - provides a generic set of interfaces for reading a line from a T. FileNamePolicy<T> - provides GetFilename() for a T.

So, if T is a CStdio derivative, or a FILE* the above will do their best to provide a common interface for seeking, reading lines, and retrieving the original file name.

Additionally, I Have:
FilteredStringReader<F,R> which marries a filter to a reader. Previously, I was doing this as:

template <typename Filter, typename Reader>
class FilteredStringReader
{
    Filter      m_filter;
    Reader &    m_reader;

public:

// Constructors
    FilteredStringReader(
        Filter      filter,
        Reader &    reader
    ) :
        m_filter(filter),
        m_reader(reader)
    {
    }

    bool ReadString(CString & strLine)
    {
        return ReadFilteredString(m_reader, m_filter, strLine);
    }
};

This works well for any algorithms that use a LineReaderPolicy<>, because the default policy is to attempt to use the ReadString() interface, and this interface matches the default (generic) policy, and life is good.

However, if this object is passed into one of the algorithms that needs to use one of the other policies - such as StreamPositionPolicy<FilteredStringReader<F,R>>, then this scheme breaks down! There isn't a StreamPositionPolicy<> for a FilteredStringReader<>, and a FilteredStringReader<> doesn't fit a default StreamPositionPolicy<> (it only provides the line reader interface, not the stream interface or name interface, etc.)

So, I was thinking that such a mixin should probably use the CRTP, and derive from its underlying file-type/reader-type. Then, it would be one of those, and any policy classes which have specializations for the underlying reader would succeed.

But that brings up lifetime / ownership / copying issues:

template <typename Filter, typename Reader>
class FilteredStringReader : public Reader
{
    Filter      m_filter;

public:

// Constructors
    FilteredStringReader(
        Filter      filter,
        Reader &    reader
    )
        : Reader(reader)
        , m_filter(filter)
    {
    }

    bool ReadString(CString & strLine)
    {
        return ReadFilteredString(m_reader, m_filter, strLine);
    }
};

Surprisingly, this sort of works - constructing this policy object is possible... but it copies the reader-instance (which may not be a grand idea, depending on the implementation of the reader - or more likely - some reader types simply won't allow a copy).

I want just a single instance of my reader object - one that is wrappered by the mixin template instance, and nothing more.

So, I feel like this is going down the wrong path.

I can make use of variadic templates and possibly use perfect forwarding to have my mixin construct itself + its base in-place. But that loses some of the functionality of the previous incarnation: the original version of FilteredStringReader<F,R> shown could be layered on top of the reader, used, and then discarded, while the lifetime of the reader itself continued (or was wrappered more deeply for another algorithm's purpose).

So, using CRTP seems like a bad fit. But then I'm back to the original problem of how to make wrappers for a type R which intercepts just one interface while leaving all others alone?

Mordachai
  • 9,412
  • 6
  • 60
  • 112
  • Is there a way to specialize `StreamPositionPolicy` for the case where T is actually a `FilteredStringReader`? That could allow me to generate the correct policy for such cases... – Mordachai Mar 21 '14 at 14:56

1 Answers1

1

You could try providing partial specializations of the policies for filters that defer to the underlying reader's policy:

template <typename Filter, typename Reader>
class FileNamePolicy<FilteredStringReader<Filter, Reader>>: public FileNamePolicy<Reader> {};

Assuming the policy methods take the reader by reference, you then just need to provide conversion operators:

template <typename Filter, typename Reader>
class FilteredStringReader
{
    Filter      m_filter;
    Reader &    m_reader;

public:
    operator Reader &() { return m_reader; }
    operator const Reader &() const { return m_reader; }
    // ...
};
ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • So for each wrapper I write, in order to support other policies, I have to write such partial specializations? This is doable, but sounds like it will scale poorly, no? – Mordachai Mar 21 '14 at 15:01
  • @Mordachai yes, it's m*n in the number of wrappers and specializations. It does have the advantage of acting as a warrant that the policy transfers correctly from the wrapped to the wrapper, which might not always be the case. – ecatmur Mar 21 '14 at 15:06
  • 1
    @Mordachai it should work - see http://rextester.com/CRQ49042. An alternative might be to resolve the policy through a metafunction, but then you'd need to change existing code to call the metafunction. – ecatmur Mar 21 '14 at 16:27
  • This works. [THANK YOU] It requires having a BaseType accessor, as you point out, and it has to be operator based unless I spell out the partial specializations, which is probably fine (though one wonders if a silent gotcha will emerge later - wrong interface called due to auto-convert). This is, however, ultimately no where near as elegant as Smalltalk's ability to build wrapper objects around other objects ad infinitum and simply forward unknown functions to the underlying object with a simple code hook. I can't help but wonder if there is a better way to do all of this?! – Mordachai Mar 21 '14 at 17:16