4

Mixins and function templates are two different ways of providing a behavior to a wide set of types, as long as these types meet some requirements.

For example, let's assume that I want to write some code that allows me to save an object to a file, as long as this object provides a toString member function (this is a rather silly example, but bear with me). A first solution is to write a function template like the following:

template <typename T>
void toFile(T const & obj, std::string const & filename)
{
    std::ofstream file(filename);
    file << obj.toString() << '\n';
}
...
SomeClass o1;
toFile(o1, "foo.txt");
SomeOtherType o2;
toFile(o2, "bar.txt");

Another solution is to use a mixin, using CRTP:

template <typename Derived>
struct ToFile
{
    void toFile(std::string const & filename) const
    {
        Derived * that = static_cast<Derived const *>(this);
        std::ofstream file(filename);
        file << that->toString() << '\n';
    }
};

struct SomeClass : public ToFile<SomeClass>
{
    void toString() const {...}
};
...
SomeClass o1;
o.toFile("foo.txt");
SomeOtherType o2;
o2.toFile("bar.txt");

What are the pros and cons of these two approaches? Is there a favored one, and if so, why?

Community
  • 1
  • 1
Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
  • 3
    Better still, modify the first one to say `toString(obj)` rather than `obj.toString()`. Then you can overload `toString` to serialise anything, including non-class types and classes that you can't add members to. – Mike Seymour Jan 18 '12 at 13:44

4 Answers4

5

The first approach is much more flexible, as it can be made to work with any type that provides any way to be converted to a std::string (this can be achieved using traits-classes) without the need to modify that type. Your second approach would always require modification of a type in order to add functionality.

Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
3

Pro function templates: the coupling is looser. You don't need to derive from anything to get the functionality in a new class; in your example, you only implement the toString method and that's it. You can even use a limited form of duck typing, since the type of toString isn't specified.

Pro mixins: nothing, strictly; your requirement is for something that works with unrelated classes and mixins cause them to be become related.

Edit: Alright, due to the way the C++ type system works, the mixin solution will strictly produce unrelated classes. I'd go with the template function solution, though.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • How can the mixin access the protected members of the derived class? It would be possible if the inheritance was the other way around, but that would be much less useful (unless I misunderstood something?). – Luc Touraille Jan 18 '12 at 13:32
  • @LucTouraille: ehm, good point. Thinko on my part, removed that. – Fred Foo Jan 18 '12 at 13:36
  • I'm not sure it is correct to say that two classes injected with the same mixin are related: think about the [boost::operators](www.boost.org/doc/libs/release/libs/utility/operators.htm) mixin; would you say that all classes that provide the common operators thanks to this mixin are related? Even from the point of view of the language, they are not really related: they don't know about each other and they don't share a common base class (`Mixin` and `Mixin` are unrelated). – Luc Touraille Jan 18 '12 at 13:57
  • I agree about the looser coupling, though: there is a unidirectional dependency between the function template and the type, while the dependency between the mixin and the type is bidirectional (hence the impossibility to handle third-party and built-in types). – Luc Touraille Jan 18 '12 at 13:58
  • @LucTouraille: alright, the two classes would not be related per the C++ type system, but I think that's only because of how templates work. In a broader OOP sense I would say they are (weakly) related because they share a common template base class. – Fred Foo Jan 18 '12 at 14:16
2

I would like to propose an alternative, often forgotten because it is a mix of duck-typing and interfaces, and very few languages propose this feat (note: very close to Go's take to interfaces actually).

// 1. Ask for a free function to exist:
void toString(std::string& buffer, SomeClass const& sc);

// 2. Create an interface that exposes this function
class ToString {
public:
  virtual ~ToString() {}

  virtual void toString(std::string& buffer) const = 0;
}; // class ToString

// 3. Create an adapter class (bit of magic)
template <typename T>
class ToStringT final: public ToString {
public:
  ToStringT(T const& t): t(t) {}

  virtual void toString(std::string& buffer) const override {
    toString(buffer, t);
  }

private:
  T t;                  // note: for reference you need a reference wrapper
                        // I won't delve into this right now, suffice to say
                        // it's feasible and only require one template overload
                        // of toString.
}; // class ToStringT

// 4. Create an adapter maker
template <typename T>
ToStringT<T> toString(T const& t) { return std::move(ToStringT<T>(t)); }

And now ? Enjoy!

void print(ToString const& ts); // aka: the most important const

int main() {
  SomeClass sc;
  print(toString(sc));
};

The two stages is a bit heavyweight, however it gives an astonishing degree of functionality:

  • No hard-wiring data / interface (thanks to duck-typing)
  • Low-coupling (thanks to abstract classes)

And also easy integration:

  • You can write an "adapter" for an already existing interface, and migrate from an OO code base to a more agile one
  • You can write an "interface" for an already existing set of overloads, and migrate from a Generic code base to a more clustered one

Apart from the amount of boiler-plate, it's really amazing how you seamlessly pick advantages from both worlds.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I'm sure it's probably because it's late, but could you elaborate slightly more on what this buys you over "option 1"'s free function approach? – Mark B Jan 21 '12 at 05:51
  • @MarkB: the main idea here is to get the advantage of interface code (loose coupling). The user of the `ToString` base class is only tied to that base class. Dependency management is usually an issue in large projects, and this approach solves it without sacrificing the goodness of duck-typing. – Matthieu M. Jan 21 '12 at 13:42
0

A few thoughts I had while writing this question:

Arguments in favor of template functions:

  • A function can be overloaded, so third-party and built-in types can be handled.

Arguments in favor of mixins:

  • Homogeneous syntax: the added behavior is invoked like any other member functions. However, it is well known that the interface of a C++ class includes not only its public member functions but also the free functions that operates on instances of this type, so this is just an aesthetic improvement.
  • By adding a non-template base class to the mixins, we obtain an interface (in the Java/C# sense) that can be use to handle all objects providing the behavior. For example, if we make ToFile<T> inherits from FileWritable (declaring a pure virtual toFile member function), we can have a collection of FileWritable without having to resort to complicated heterogeneous data structures.

Regarding usage, I'd say that function templates are more idiomatic in C++.

Luc Touraille
  • 79,925
  • 15
  • 92
  • 137
  • 1
    The syntax is only homogeneous if you were using methods before, which not all C++ programs do; notably, C++11 introduced a host of new template functions in the standard library to handle containers and iterators. – Fred Foo Jan 18 '12 at 13:33
  • @larsmans: You are right, many C++ libraries (not only the standard one) adopt the generic programming paradigm and hence use free functions extensively. – Luc Touraille Jan 18 '12 at 13:39