0

I am implementing a generic work engine class that executes field-specific tasks. All fields shall be derived from a base class so polymorphic behavior will be used. I created overloaded functions with type-specific to derived fields, i.e work_engine::run(const a_field& af) in the below code. I've assumed whenever a field comes (the base class type), the appropriate function will be called automatically. However, I have got the error below (refers // magic line):

Error   C2664    'void work_engine::run(const b_field &)': cannot convert argument 1 from 'base_field' to 'const a_field &' 

I used to use this approach in C#, but I am not familiar with it in C++. By using C++11 and later features, I would like to implement the same approach, as it is cleaner code than using if-else statements with cast operations. On the other hand, perhaps I am doing primitive mistakes, so I'd like to separate my questions into two items:

  1. What is the proper way of achieving my intention?
  2. What is the origin of the error I've met?

Thanks in advance for all your comments,

Header1.h including class definitions is below:

#pragma once
#include <algorithm>
#include <list>

// abstract class of all fields 
class base_field
{
public:
    base_field() {}
    virtual ~base_field() {}
};

// custom a field
class a_field : public base_field
{
public:
    a_field() : base_field(){}
};

// custom b field
class b_field : public base_field
{
public:
    b_field() : base_field() {}
};

class work_engine
{
public:
    std::list<base_field> fields;
private:
    void run(const a_field& af) {}
    void run(const b_field& bf){}
public:
    void run_all()
    {
        for_each(fields.begin(), fields.end(), [&](auto& el) { this->run(el); }); // magic line
    }
};

An the main below:

#include <iostream>
#include <Header1.h>

int main()
{
    work_engine engine;
    engine.fields.push_back(a_field());
    engine.fields.push_back(b_field());
    engine.run_all();
}

In other words, what I am looking for is implicit cast to concrete class inside the lambda expression, referring to the // magic line.

Abdullah
  • 43
  • 8
  • Does this answer your question? [Vectors and polymorphism in C++](https://stackoverflow.com/questions/16126578/vectors-and-polymorphism-in-c) – Lukas-T Dec 20 '19 at 11:41
  • Not actually, My question is more about implicit casting to concrete class inside the lambda the expression. // magic line – Abdullah Dec 20 '19 at 12:02
  • It is onr of your problems. In C++ that is a list of instances of concrete type, not a list of references of polymorphic type. Yhe second half is that you need the visitor pattern, either manually, via variant, or by writing some machinery yourself. – Yakk - Adam Nevraumont Dec 20 '19 at 12:20

3 Answers3

0

First of all: You need a std::list<base_field *> as detailed in this question.

Secondly, downcasting must be done explicitly and you can't pick the appropriate function overload at runtime automatically.


One solution could be to use a (pure) virtual function on base_field

class base_field
{
public:
    base_field() {}
    virtual ~base_field() {}

    virtual void Run() = 0;
};

// custom a field
class a_field : public base_field
{
public:
    a_field() : base_field(){}

    void Run(work_engine &engine) { engine.run(*this); }
};

There you either need to make work_engine::run public or make it a friend of a_field (or b_field respectivly).


The alternative would be to use type checking at runtime. If you have a base_field *base you can check the type like this:

auto aPtr = dynamic_cast<a_field *>(base);

if(aPtr != nullptr) {
    this->run(*aPtr);
}
else {
    auto bPtr = dynamic_cast<b_field *>(base);

    if(bPtr != nullptr) {
        this->run(*bPtr);
    }
    else {
        // Ooops, it's something else entirely.    
    }
}

That's just a basic outline, but I hope it helps.

Lukas-T
  • 11,133
  • 3
  • 20
  • 30
0
std::vector<std::unique_ptr<base_field>> fields;

if you want polymorphism don't use values. Also std list is a special case container in C++; use vector by default.

struct a_field;
struct b_field;
struct field_visitor{
  virtual void operator()(a_field const&)=0;
  virtual void operator()(b_field const&)=0;
};
template<class F>
struct visitor:field_visitor{
  F f;
  visitor(F in):f(in){}
  virtual void operator()(a_field const& a){ f(a); };
  virtual void operator()(b_field const& b){ f(b); };
};



class base_field {
public:
  virtual void visit(field_visitor&)const=0;
// ...

class a_field : public base_field {
public:
  virtual void visit(field_visitor& v){v(*this);}
//...
class b_field : public base_field {
public:
  virtual void visit(field_visitor& v){v(*this);}
//...
void run_all() {
    for_each(fields.begin(), fields.end(), [&](auto&& el) { if(el) el->visit( visitor{ [&](auto& field){this->run(field); } });});
}

there are a bunch of other ways to do this. (This one is but in a style).

Another option is to store a variant<a_field, b_field> and std::visit it. Another option is to write a runnable type erasure type.

std::vector<std::variant<a_field, b_field>> fields;
//...
void run_all() {
    for_each(fields.begin(), fields.end(), [&](auto&& el) { std::visit( el, [&](auto&& x){ this->run(x); } ); });
}

the variant version is notable in that a_field and b_field need not be related types, just run has to accept them. So one could be std::string the other double.


C++ does not carry a complier around at run or link time (usually), so code at the point of use has to know what types it is written for. Vtable dispatch goes one way, and isn't extended due to requests outside of the class definition.

In comparison, C# carries a compiler around; so it can add a run dynamic dispatch based on two separate pieces of code automatically.

In C++, in double dispatch, one of the handled set of sub types should be listed. And in single dispatch, it needs be done within the type.

There are ways to stretch this, but not as far as C#.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

I see using values instead of pointers cause object slicing: I changed values with pointers, shared_ptr in modern C++. The second, I moved run function into each field, so the field-specific function is encapsulated with the related field.

#pragma once
#include <algorithm>
#include <list>

// abstract class of base fields 
class base_field
{
public:
    base_field() {}
    virtual ~base_field() {}
    virtual void run() = 0;
};

// custom a field
class a_field : public base_field
{
public:
    a_field() : base_field(){}
    virtual void run() override{}
};

// custom b field
class b_field : public base_field
{
public:
    b_field() : base_field() {}
    virtual void run() override {}
};

class work_engine
{
public:
    std::list<std::shared_ptr<base_field>> fields;
private:

public:
    void run_all()
    {
        for_each(fields.begin(), fields.end(), [&](auto& el) { el->run(); });
    }
};

With those changes, the error is disappeared as well as working as intended.

Abdullah
  • 43
  • 8