Silvio Mayolo
Silvio Mayolo

Reputation: 70287

"Pimp my Library" in C++

In Scala, there's a design pattern often called "pimp my library". The basic idea is that we have some class Foo (presumably in some library that we can't modify), and we want Foo to act like it has some method or behavior frobnicate, we can use an implicit class to add the method after the fact.

implicit class Bar(val foo: Foo) extends AnyVal {
  def frobnicate(): Unit = {
    // Something really cool happens here ...
  }
}

Then, if we have an instance of Foo, we can call frobnicate on it and, as long as Bar is in scope, the Scala compiler will be smart enough to implicitly cast Foo to Bar.

val foo = new Foo()
foo.frobnicate() // Correctly calls new Bar(foo).frobnicate()

I'd like to do this same sort of thing in C++. I know that C++ has implicit casts, but they don't seem to trigger when accessing members. For a concrete example, the following code in C++ produces an error.

class Foo {}; // Assume we can't modify this class Foo

class Bar {
private:
  Foo foo;
public:
  Bar(Foo foo) : foo(foo) {}
  void frobnicate() {
    cout << "Frobnicating :)" << endl;
  }
};

void frobnicate(Bar bar) {
  cout << "Frobnicating :)" << endl;
}

int main() {
  Foo value;
  frobnicate(value);  // This works
  value.frobnicate(); // But this doesn't
  return 0;
}

On the value.frobnicate() line, C++ doesn't seem to look for implicit conversions in that context.

main.cc:30:9: error: ‘class Foo’ has no member named ‘frobnicate’

Note: I'm compiling with C++11 right now. At a cursory glance at the newer C++ versions, it doesn't seem like anything that would affect this question has changed since then. C++11-compatible solutions are ideal, but a way to do this in newer C++ versions would be good as well, for didactic purposes.

Upvotes: 4

Views: 458

Answers (4)

Pharap
Pharap

Reputation: 4082

Not exactly, but...

#include <iostream>

class Foo
{
public:
    void nothingUpMySleeve()
    {
        std::cout << "Foo!\n";
    }
};

class Bar
{
private:
    Foo & foo;

public:
    Bar(Foo & foo) : foo { foo }
    {
    }

    void frobnicate()
    {
        std::cout << "Frobnicating\n";
    }

    Foo * operator ->()
    {
        return &this->foo;
    }

    operator Foo &()
    {
        return this->foo;
    }
};

void thisFunctionOnlyAcceptsFoo(Foo & foo)
{
    std::cout << "Yes, that's a Foo\n";
}

void useExtendedType(Bar wrapper)
{
    // Use the extension function
    wrapper.frobnicate();

    // Use arrow notation to use the original functions
    wrapper->nothingUpMySleeve();

    // Implicit conversion kicks in when necessary
    thisFunctionOnlyAcceptsFoo(wrapper);
}

int main()
{
    Foo value;

    // Pass to a function that uses Bar
    useExtendedType(value);
}

I really don't recommend doing this in actual code though. It's just a cheap party trick.

It's not even clear whether Bar should own a copy of Foo or just reference it. I opted for just referencing because I'm presuming some other code is managing the lifetime of Foo, and this way Bar can be created and destroyed incredibly cheaply. You just have to be careful that Bar's lifetime is always shorter than Foo's, otherwise it's nasal demons I'm afraid.

Upvotes: 1

user4442671
user4442671

Reputation:

What you are describing is called Unified Function Call Syntax (UFCS), and cannot be done in C++ as of today. Anyone who has worked with ranges and algorithm chaining in D can attest to how amazing that would be to have.

The lack of UFCS support is actually one of the main reason why the general modern wisdom is to use free-floating functions and ADL for anything that does not need to be virtual or encapsulation.

There has been proposals to bring D's UFCS to the language, but it's still just a proposal:

https://isocpp.org/files/papers/N4165.pdf

Edit: For anyone wondering why we would ever want that feature, imagine being able to write the following:

std::vector<int> foo(const std::vector<int>& v) {
  return v.filter([](int x) {return x > 5;})
          .map([](int x) {return x*x;})
          .sort();
}

Upvotes: 10

Edward Strange
Edward Strange

Reputation: 40867

C++ doesn't seem to look for implicit conversions in that context.

That is correct. The mechanism you're looking for doesn't exist in the language.

Upvotes: 1

IlBeldus
IlBeldus

Reputation: 1040

While you can't do exactly what you are asking you can inherit Foo and extend it.

class Bar : public Foo{
public:
void frobnicate() {
    cout << "Frobnicating :)" << endl;
  }
}

now Bar has everything Foo has plus the new method. see http://www.cplusplus.com/doc/tutorial/inheritance/

Upvotes: 3

Related Questions