I am trying to create a vector of different types of objects that all satisfy a given concept. My current attempt is something like this:
template<typename T>
concept Foo = requires(T t) {
// constraints...
};
std::vector<std::unique_ptr<Foo>> getFooVector();
I get that Foo
is not a complete type, and I think I would have to use type erasure to make this work, but I am not quite sure how I would go about that. How could I make something like this work?
I am trying to create a vector of different types of objects
This, strictly speaking is impossible, in the sense that all the objects that a std::vector<T>
can hold, must all be of type T
.
Whether that T
is a pointer (smart or not) and so can, in turn, point to entities of different type, that's another story.
So I guess what you want can be obtained in two similar but not so similar ways:
If you don't know upfront the types that you want to make a collection of, go for
struct Foo {
/* the interface all Foo must satisfy, e.g. ... */
virtual int f(int) = 0;
};
struct Bar : Foo {
virtual int f(int) override;
};
struct Baz : Foo {
virtual int f(int) override;
};
std::vector<std::unique_ptr<Foo>> getFooVector();
so in getFooVector
's body you can push as many objects of classes inheriting from Foo
as you want.
In this case, the constraint you wanted to enforce via concept, is enforced via inheritance: if Foo
declares a pure virtual
method, the derived classes must override it, i.e. they have to implement that behavior.
If you do know the types upfront, you can use std::variant
std::vector<std::variant<Bar,Baz>> getFooVector();
Now, if you really want Bar
and Baz
to be constrained to implement some common interface, I guess you can still make them inherit from a common base, but probably the compiler will be able to avoid any virtual indirection, which is good.
Also remember that if the types have just a very simple interface, e.g. one method (int(int)
for simplicity below), you could also give a try to a not-my-inheritance approach, by baking the virtuality in a library type named std::function
:
struct Foo {
std::function<int(int)> const& f;
};
auto foo1 = Foo{[](int i){ return i*2; }};
auto foo2 = Foo{[](int i){ return i*3; }};
where foo1
and foo2
are both truly of type Foo
, but behave pretty much the same way as an instance of Baz
and one of Bar
in the first bullet point respectively.
The why std::function
manages to be able to store different lambdas (which by definition have different types) and any other callable is that it uses type erasure, a technique that deserves its own post.
I think concepts are the wrong tool for your usecase.