How to write an input filter that is policy-friendly?

60 Views Asked by At

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?

1

There are 1 best solutions below

4
On

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; }
    // ...
};