2

I am trying to create function pointers for functions that return unique_ptr instances. Each function should return a value that is as specifically-typed as possible in order to be generally useful to many callers (in my real code, the functions are named constructors and in the public header for each object). However, in this particular use, I only care about the general interface that each class implements.

I am running into an issue where I cannot assign a function that returns unique_ptr<Subclass> to a function pointer that returns unique_ptr<Superclass>.

I have boiled my example down to this snippet:

#include <iostream>
#include <memory>

struct Foo {
  virtual void foo() = 0;
};

struct Bar : public Foo {
  void foo() {};
};

std::unique_ptr<Foo>
foo_creator()
{
  return nullptr;
}

std::unique_ptr<Bar>
bar_creator()
{
  return nullptr;
}

typedef std::unique_ptr<Foo>(*creator_fn)();

int
main(int argc, char *argv[])
{
  std::unique_ptr<Foo> f;

  f = foo_creator();
  f = bar_creator();

  creator_fn foo_fn = foo_creator;
  creator_fn bar_fn = bar_creator; // Fails

  return 0;
}

The compilation error I get from clang (Apple LLVM version 4.2 (clang-425.0.24) (based on LLVM 3.2svn)) is:

cannot initialize a variable of type 'creator_fn'
(aka 'std::unique_ptr<Foo> (*)()') with an lvalue
of type 'std::unique_ptr<Bar> ()':

different return type
('unique_ptr<struct Foo>' vs 'unique_ptr<struct Bar>')

I'm open to being told of a better way of accomplishing my goal, too. :-)

ildjarn
  • 62,044
  • 9
  • 127
  • 211
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    Is it by intention that both `foo_creator` and `bar_creator` create objects with this line: `std::unique_ptr(new Bar())`? Shouldn't `bar_creator` create `Bar`s? – Lii Mar 17 '13 at 01:35
  • @Lii The content of those methods isn't really relevant to the problem. I think I could just return `nullptr` and get the same issue. In the larger code, I actually have two classes deriving from `Foo`, but this seemed simpler. - I'll edit the code to be nullptr to remove any extra confusion. – Shepmaster Mar 17 '13 at 01:38

2 Answers2

2

The feature you would need for this code to compile is called covariance of type arguments. Have a look at the Wikipedia article on the subject.

For this to work, the fact that Bar is a subtype to Foo would have to imply that unique_ptr<struct Bar> would be a subtype to unique_ptr<struct Foo>.

Some languages have this property, but C++ does not, making it harder to get templates and inheritance to play nicely together. In Java you would write this:

UniquePtr<? extends Foo> f;
f = fooCreator();
f = barCreator();

Where UniquePtr<? extends Foo> f declares a variable that is covariant in its type argument.

I guess you have to find a design that works around this in some way. This post seem to contain a similar question and might give some suggestions.

EDIT: I misread the question and thought the compile error was on the wrong line so the answer above is a bit misleading. I still think the lack of variance is the problem but the error arise on this line: bar_fn = bar_creator; // Fails

Community
  • 1
  • 1
Lii
  • 11,553
  • 8
  • 64
  • 88
  • Are you sure it has to do with covariance? For the line `f = bar_creator()`, I am returning a `unique_ptr` and saving it into a `unique_ptr` without any warnings. It is only when I bring in a function pointer that problems arise. – Shepmaster Mar 17 '13 at 01:41
  • Perhaps `unique_ptr` is smart enough to correctly assign from another `unique_ptr` of a derived class, but the compile-time type system isn't - which **would** make it a covariance issue? – Shepmaster Mar 17 '13 at 01:51
  • 1
    Hm, sorry, I seem to have read you code a bit sloppily, my answer is not correct. I don't understand why you don't get an error at the `f = bar_creator();` line and on the return line in `foo_creator` (on both those places the type arguments do not match). I was under a strong impression that C++ didn't supported any kind of variance. Maybe I don't know this good enough to try to answer questions about it... – Lii Mar 17 '13 at 01:58
  • 1
    "Perhaps unique_ptr is smart enough...". That is my guess also. Some trick with copy-constructors and assignment operators make it work. The answer to [this post](http://stackoverflow.com/questions/639248/c-covariant-templates?rq=1) mention exactly this. For functions pointer variables this doesn't work and covariant type parameter would be necessary. – Lii Mar 17 '13 at 02:48
1

Here is the modified design I used to solve my particular problem. I used the new C++11 template using declaration to mark which specific type will be returned. When I call the function, I leverage unique_ptrs ability to upcast with no problem.

template <class T>
using creator_fn = std::unique_ptr<T>(*)();

int
main(int argc, char *argv[])
{
  std::unique_ptr<Foo> f;

  f = foo_creator();
  f = bar_creator();

  creator_fn<Foo> foo_fn = foo_creator;
  creator_fn<Bar> bar_fn = bar_creator;

  f = foo_fn();
  f = bar_fn();

  return 0;
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366