Is there an equivalent to type traits from C++ in other languages?

319 Views Asked by At

I like the concept of type traits, because it solves some design questions in a clear and extensible way. For example, imagine we have a couple of printer classes and document classes.

Since new document types may be added later, the printer should not directly know which document it is printing, so that it doesn't have to be adjusted then. On the other hand, documents shouldn't know about all printers.

By providing traits about how to print documents, this is the only place that must be modified when adding new documents. Moreover, if there are new printers, they can use the same traits directly and focus on the internal printing process that differs from the other printers.

// Documents
class pdf { /* ... */ };
class txt { /* ... */ };

// Traits
template<typename t>
struct traits; // No default trait, so new documents throw compilation error

template<>
struct traits<pdf> {
    static std::string get_title(pdf& document) { /* ... */ }
    static std::string get_content(pdf& document) { /* ... */ }
    static bool has_images = true;
};

template<>
struct traits<txt> {
    static std::string get_title(txt& document) { return ""; } // Not supported
    static std::string get_content(txt& document) { /* ... */ }
    static bool has_images = false;
};

// Printers
class canon_printer : public printer {
    template<typename T>
    void print(T document) override
    {
        std::string title = traits<T>.get_title(document);
        // ...
        if (traits<T>.has_images)
            // ...
    }
};

To me, it looks quite powerful. As of now, I have only seen this concept in C++. Is there a similar concept in other programming languages? I'm more looking for a language independent term for this approach rather than a list of languages.

1

There are 1 best solutions below

1
On BEST ANSWER

It depends on what you're using the traits for. For many uses, some form of the strategy pattern would be an appropriate solution. You'll get run time resolution, rather than compile time, and you can't really use it to define types, however.

In your example, I'm not sure that you want traits even in C++. Your function Printer::print is apparently virtual (since you override it in a derived class), but is also a template; this is not legal in C++. What you probably need is some variant of the bridge pattern, using virtual functions even in C++. You might, for example, have an abstract base class along the lines of:

class DocumentInformation
{
public:
    virtual ~DocumentInformation() = default;
    virtual std::string get_title() const = 0;
    virtual std::string get_content() const = 0;
    //  ...
    virtual bool has_images() const = 0;
};

Then for each document type, you derive a concrete instance:

class PDFDocumentInformation : public DocumentInformation
{
    PDFDocument const& myDocument;
public:
    PDFDocumentInformation( PDFDocument const& document )
        : myDocument( document )
    {
    }
    std::string get_title() const override
    {
        //  ...
    }
    // ...
    bool has_images() const override { return true; }
};

You're (virtual) Printer::print function then takes a DocumentInformation const&, and uses it to obtain the information it needs.

Depending on the way your code is organized, you might not even need the bridge. It's often possible to have all of your concrete document classes derived directly from an abstract Document, with the virtual functions, and pass this to Printer::print. This is, in fact, the usual case; the bridge is only needed if you have existing document classes with incompatible interfaces.