Rae_III
Rae_III

Reputation: 39

Is there a good way to implement a template interface in C++?

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

Upvotes: 1

Views: 1234

Answers (5)

MikeMB
MikeMB

Reputation: 21166

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;
    }
}  

Upvotes: 0

M.M
M.M

Reputation: 141648

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.

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490633

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.

Upvotes: 3

Pradhan
Pradhan

Reputation: 16777

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.

Upvotes: 0

Severin Pappadeux
Severin Pappadeux

Reputation: 20130

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

Upvotes: 0

Related Questions