1

I've been looking around for a way to do this, and I'm not sure it's even possible. I've got a class in Java that takes an instance of a generically-typed interface as part of its constructor, and I'd like to recreate it in C++ (it's a utility class that is handy in many situations). To the best of my understanding, the closest equivalent to an interface in C++ is a pure virtual class, and the (somewhat) equivalent of generics is templates.

So let's say I have some classes defined as follows:

template<typename R>
class AnInterface
{
    public:
        virtual R run() = 0;
        virtual ~AnInterface() {}
};

template<typename R>
class Processor
{
    public:
        Processor(std::vector<AnInterface<R>> toRun) : toRun(toRun) {}
        std::vector<R> process() {
            std::vector<R> res;
            for(int i = 0; i < this->toRun.size(); ++i)
               res.push_back(toRun[i].run());
            return res;
            }

    private:
        std::vector<AnInterface<R>> toRun;
};


class AnInstanceClass : public AnInterface<int>
{
    int run() { return 1+1; }
};

I'd like to be able to do something like this with them:

int main()
{
    std::vector<AnInterface<int>> toRun;
    toRun.push_back(AnInstanceClass());
    toRun.push_back(AnInstanceClass());
    Processor<int> p(toRun);
    std::vector<int> p.process();
}

Basically, have a class who's job is to take a list of objects, run them, and then return a list of their results, while being agnostic to the types of objects and results (assuming that the objects have a 'run' function). In Java, I accomplished this with generics and interfaces. I tried implementing the above solution in C++, but it doesn't compile and the compiler output is very cryptic, suggesting that I'm screwing up something very fundamental to the language. My C++ is a little rusty, so I'm not exactly sure what that is. How can something like this be implemented in C++?

Edit: Here's the error message when I try to compile the above code:

In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h: In instantiation of ‘class std::vector<AnInterface<int> >’:
test.cpp:36:36:   required from here
/usr/include/c++/4.8/bits/stl_vector.h:704:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       resize(size_type __new_size, value_type __x = value_type())
       ^
test.cpp:4:7: note:   because the following virtual functions are pure within ‘AnInterface<int>’:
 class AnInterface
       ^
test.cpp:7:19: note:    R AnInterface<R>::run() [with R = int]
         virtual R run() = 0;
                   ^
test.cpp: In function ‘int main()’:
test.cpp:40:23: error: expected initializer before ‘.’ token
     std::vector<int> p.process();
                       ^
test.cpp: In instantiation of ‘Processor<R>::Processor(std::vector<AnInterface<R> >) [with R = int]’:
test.cpp:39:27:   required from here
test.cpp:15:68: error: no matching function for call to ‘std::vector<int, std::allocator<int> >::vector(std::vector<AnInterface<int> >&)’
         Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
                                                                    ^
test.cpp:15:68: note: candidates are:
In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note: template<class _InputIterator> std::vector<_Tp, _Alloc>::vector(_InputIterator, _InputIterator, const allocator_type&)
         vector(_InputIterator __first, _InputIterator __last,
         ^
/usr/include/c++/4.8/bits/stl_vector.h:398:9: note:   template argument deduction/substitution failed:
test.cpp:15:68: note:   candidate expects 3 arguments, 1 provided
         Processor(std::vector<AnInterface<R> > toRun) : toRun(toRun) {}
                                                                    ^
In file included from /usr/include/c++/4.8/vector:64:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note: std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = int; _Alloc = std::allocator<int>]
       vector(const vector& __x)
       ^
/usr/include/c++/4.8/bits/stl_vector.h:310:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const std::vector<int, std::allocator<int> >&’
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note: std::vector<_Tp, _Alloc>::vector(std::vector<_Tp, _Alloc>::size_type, const value_type&, const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::size_type = long unsigned int; std::vector<_Tp, _Alloc>::value_type = int; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
       vector(size_type __n, const value_type& __value = value_type(),
       ^
/usr/include/c++/4.8/bits/stl_vector.h:295:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘std::vector<int, std::allocator<int> >::size_type {aka long unsigned int}’
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note: std::vector<_Tp, _Alloc>::vector(const allocator_type&) [with _Tp = int; _Alloc = std::allocator<int>; std::vector<_Tp, _Alloc>::allocator_type = std::allocator<int>]
       vector(const allocator_type& __a)
       ^
/usr/include/c++/4.8/bits/stl_vector.h:256:7: note:   no known conversion for argument 1 from ‘std::vector<AnInterface<int> >’ to ‘const allocator_type& {aka const std::allocator<int>&}’
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note: std::vector<_Tp, _Alloc>::vector() [with _Tp = int; _Alloc = std::allocator<int>]
       vector()
       ^
/usr/include/c++/4.8/bits/stl_vector.h:248:7: note:   candidate expects 0 arguments, 1 provided
In file included from /usr/include/c++/4.8/vector:69:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc: In instantiation of ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<AnInterface<int>*, std::vector<AnInterface<int> > >; typename std::_Vector_base<_Tp, _Alloc>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/bits/stl_vector.h:913:28:   required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38:   required from here
/usr/include/c++/4.8/bits/vector.tcc:329:19: error: cannot allocate an object of abstract type ‘AnInterface<int>’
    _Tp __x_copy = __x;
                   ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/c++/4.8/vector:69:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/vector.tcc:329:8: error: cannot declare variable ‘__x_copy’ to be of abstract type ‘AnInterface<int>’
    _Tp __x_copy = __x;
        ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/x86_64-linux-gnu/c++/4.8/bits/c++allocator.h:33:0,
                 from /usr/include/c++/4.8/bits/allocator.h:46,
                 from /usr/include/c++/4.8/vector:61,
                 from test.cpp:1:
/usr/include/c++/4.8/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = AnInterface<int>; __gnu_cxx::new_allocator<_Tp>::pointer = AnInterface<int>*]’:
/usr/include/c++/4.8/ext/alloc_traits.h:216:9:   required from ‘static void __gnu_cxx::__alloc_traits<_Alloc>::construct(_Alloc&, __gnu_cxx::__alloc_traits<_Alloc>::pointer, const _Tp&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; __gnu_cxx::__alloc_traits<_Alloc>::pointer = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_vector.h:906:34:   required from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >; std::vector<_Tp, _Alloc>::value_type = AnInterface<int>]’
test.cpp:37:38:   required from here
/usr/include/c++/4.8/ext/new_allocator.h:130:9: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       { ::new((void *)__p) _Tp(__val); }
         ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
       ^
In file included from /usr/include/c++/4.8/vector:62:0,
                 from test.cpp:1:
/usr/include/c++/4.8/bits/stl_construct.h: In instantiation of ‘void std::_Construct(_T1*, const _T2&) [with _T1 = AnInterface<int>; _T2 = AnInterface<int>]’:
/usr/include/c++/4.8/bits/stl_uninitialized.h:75:53:   required from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; bool _TrivialValueTypes = false]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:117:41:   required from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*]’
/usr/include/c++/4.8/bits/stl_uninitialized.h:258:63:   required from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = __gnu_cxx::__normal_iterator<const AnInterface<int>*, std::vector<AnInterface<int> > >; _ForwardIterator = AnInterface<int>*; _Tp = AnInterface<int>]’
/usr/include/c++/4.8/bits/stl_vector.h:316:32:   required from ‘std::vector<_Tp, _Alloc>::vector(const std::vector<_Tp, _Alloc>&) [with _Tp = AnInterface<int>; _Alloc = std::allocator<AnInterface<int> >]’
test.cpp:39:27:   required from here
/usr/include/c++/4.8/bits/stl_construct.h:83:7: error: cannot allocate an object of abstract type ‘AnInterface<int>’
       ::new(static_cast<void*>(__p)) _T1(__value);
       ^
test.cpp:4:7: note:   since type ‘AnInterface<int>’ has pure virtual functions
 class AnInterface
Rae_III
  • 39
  • 1
  • 10
  • post the error message – pm100 Mar 19 '15 at 21:48
  • I posted it. It's very long an cryptic. – Rae_III Mar 19 '15 at 21:56
  • Well, to begin with, virtual functions supposed to have same signature, so difference in return type won't work. Second, if your interfaces are via virtual functions, using vector of objects won't work, you'll need pointers for this polymorphism to work – Severin Pappadeux Mar 19 '15 at 21:59
  • Hmm, is there anyway to get around virtual functions needing the same signature besides doing something like using a void pointer? – Rae_III Mar 19 '15 at 22:14

5 Answers5

3

You're basically (attempting to) re-create the functionality of std::generate. The difference is that generate doesn't rely on the somewhat clunky convention of a member function named run. Rather, it invokes something like a function (though it may, and often will, be an overloaded operator()).

We can also (frequently) avoid the separate definition of what you've named AnInstanceClass by defining the class in a lambda expression.

So, in this case, we'd be looking at something like:

std::vector<int> p;

std::generate_n(std::back_inserter(p), 2, [] { return 1 + 1; });

This is basically threading-agnostic, so if you want to run the individual tasks in separate threads, you can do that pretty easily as well. There are some caveats with std::async, but they're pretty much the same regardless of whether you involve std::generate.

Note that this is slightly different from @Severin's answer--he's mentioning std::transform instead of std::generate. The basic difference between the two is that transform takes a set of inputs, transforms them, and produces a set of those outputs. Your AnInstance::run just produces outputs (without taking any inputs) so at least to me it seems like std::generate is a better fit.

std::transform would be more useful if you had something like this:

std::vector<int> inputs { 1, 2, 3, 4, 5};
std::vector<int> results;

std::transform(inputs.begin(), inputs.end(), [](int in) { return in * 2; });

This should produce results of 2, 4, 6, 8, 10.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • Oh cool, I wasn't aware of those functions. Do you happen to know how to get std::generate or std::transform to run multi-threaded? – Rae_III Mar 19 '15 at 22:11
  • @Rae_III: Since you're apparently using `gcc`, you might want to look into its [parallel mode](https://gcc.gnu.org/onlinedocs/libstdc++/manual/parallel_mode.html). – Jerry Coffin Mar 19 '15 at 22:15
0

Do you really need Processor class? What I would propose to use std::transform

std::transform applies the given function to a range and stores the result in another range

Severin Pappadeux
  • 18,636
  • 3
  • 38
  • 64
  • Yes, I apologize for oversimplifying, but this is just an example. My code actually runs each of those in their own thread (sort of an automatic way to multi-thread). But, I'm not looking for help with threading, I'm just wondering what the correct way to do something like this is in C++ (having templated interfaces). – Rae_III Mar 19 '15 at 21:51
0

The only conceptual error you have is trying to get polymorphic behaviour when invoking virtual functions through objects, as opposed to pointers or references to said objects. In C++, to get run-time polymorphism, you need to work with pointers or references. Thus, Processor should work with a std::vector<AnInterface<R>*> like this:

template<typename R>
class Processor
{
    public:
        Processor(std::vector<AnInterface<R>*> toRun) : toRun(toRun) {}
        std::vector<R> process() {
            std::vector<R> res;
            for(int i = 0; i < this->toRun.size(); ++i)
               res.push_back(toRun[i]->run());
            return res;
            }

    private:
        std::vector<AnInterface<R>*> toRun;
};

Here's a fixed version of your code.

Another thing to note : when using overriding a virtual function in a derived class, mark the override with the eponymous keyword. This helps the compiler help you.

Pradhan
  • 16,391
  • 3
  • 44
  • 59
  • Thanks for the override hint. I had thought about using pointers before, but it didn't work when I tried it, adding in override fixed it though. – Rae_III Mar 20 '15 at 20:39
0

vector<AnInterface<R>> does not work because it causes slicing. This is also the cause of your error messages, because some vector operations require to default-construct or copy-construct objects and that is not possible with an abstract class.

Probably vector<shared_ptr<AnInterface<R>>> best matches your intent. shared_ptr is the closest thing C++ has to a Java object reference.

Here is working code in C++11 based on your sample code. One point I would have is that Processor currently takes its vector by value. It could take this by reference, or even by moving, if that better matched your design.

#include <iostream>
#include <memory>
#include <vector>

template<typename R>
struct AnInterface
{
    virtual R run() = 0;
    virtual ~AnInterface() {}
};

template<typename R>
using AnInterfaceVector = std::vector< std::shared_ptr<AnInterface<R>> >;

template<typename R>
class Processor
{
    public:
        Processor(AnInterfaceVector<R> toRun) : toRun(toRun) {}

        std::vector<R> process()
        {
            std::vector<R> res;
            for (auto && r : toRun)
                res.push_back( r->run() );

            return res;
        }

    private:
        AnInterfaceVector<R> toRun;
};

struct AnInstanceClass : AnInterface<int>
{
    int run() override { return temp; }

    AnInstanceClass(int n): temp(n) {}
    int temp;
};

int main()
{
    AnInterfaceVector<int> toRun;

    toRun.emplace_back( std::make_shared<AnInstanceClass>(4) );
    toRun.emplace_back( std::make_shared<AnInstanceClass>(7) );

    Processor<int> p{toRun};
    auto results = p.process();

    for (auto && i : results)
        std::cout << i << " ";
    std::cout << std::endl;
}

NB. I don't offer any claim whether this is better or worse than using a different pattern as other answers have suggested; this is just a working version of the code you were trying to write.

Community
  • 1
  • 1
M.M
  • 138,810
  • 21
  • 208
  • 365
0

As was already mentioned in the other answers, your error was trying to use a vector of interfaces (std::vector<AnInterface<int>>) instead of a vector of pointers to interfaces like std::vector<AnInterface<int>*> - with only the latter allowing polymorphism, whereas your version would try to store actual Interface objects (which is of course not posssible as they are abstract classes).

I wanted to mention in addition, that there is a nice pattern by Sean Parent that makes it unnecessary for your AnInstanceClass to inhereit from anything, as long as it implements a member function with the correct name and signature. This is quite handy, because you can e.g. even use lambdas or plain functions (after wrapping them in a std::function) which cannot inherit from anything:

#include <vector>
#include <memory>
#include <iostream>
#include <algorithm>
#include <functional>

//R is the return type
template<class R>
class Processor {
public:
    //T can be anything, that has an ()-operator
    template<class T>
    void push_back(const T& arg) {
        todo.emplace_back(std::make_unique<runnable_imp<T>>(arg));
    }

    std::vector<R> process() {
        std::vector<R> ret;
        for (auto& e : todo) {
            ret.push_back(e->run());
        }
        return ret;
    }

private:
    struct runnable_concept {
        virtual R run()=0;
        virtual ~runnable_concept(){};
    };

    template<class T>
    struct runnable_imp :public runnable_concept {
        runnable_imp(T data) :data(data){};         
        virtual R run() override { return data(); }
        T data;
    };
    std::vector<std::unique_ptr<runnable_concept>> todo;
};

struct SomeClass {
    SomeClass(int arg) :arg(arg){};
    int operator()(){ return arg; }
    int arg;
};
int SomeFunction(){ return 30; }

int main()
{
    Processor<int> pr;
    pr.push_back([]{return 10; }); 
    pr.push_back(SomeClass(20)); 
    pr.push_back(std::function<int()>(SomeFunction));
    std::vector<int> res= pr.process();

    for (auto e : res) {
        std::cout << e << std::endl;
    }
}  
MikeMB
  • 20,029
  • 9
  • 57
  • 102