2

I have written a function that determines which class object is passed, using dynamic_casting. Inside the condition, can I use static_cast to actually cast the object?

For example, inside someFunc()

class Base
{
public:
    Base() { cout << "Base::Base" << endl; }
    virtual ~Base() { cout << "Base::~Base" << endl; }
};

class Derived1 : public Base
{
public:
    void func1() { cout << "Derived1::func1()" << endl; }
};

class Derived2 : public Base
{
public:
    void func2() { cout << "Derived2::func2()" << endl; }
};

void someFunc(Base * bp)
{
    if(dynamic_cast<Derived1 *>(bp))
    {
        Derived1 * d1 = static_cast<Derived1 *>(bp); // static_cast ok?
        d1->func1();
    }
    else if(dynamic_cast<Derived2 *>(bp))
    {
        Derived2 * d2 = static_cast<Derived2 *>(bp); // static_cast ok?
        d2->func2();
    }
    else
        cout << "None" << endl;
}
int main()
{
    Derived1 * derived1 = new Derived1;
    Derived2 * derived2 = new Derived2;
    vector<Base *> vb;
    vb.push_back(derived1);
    vb.push_back(derived2);

    // ---- Passing to someFunc() ----
    someFunc(vb.at(0));
    someFunc(vb.at(1));
}
r18ul
  • 1,082
  • 2
  • 12
  • 30

4 Answers4

4

It is OK, but there are some cases (involving multiple inheritance and virtual bases) where you can dynamic cast, but not static cast.

However, that is all irrelevant. There is a much simpler way of doing what you want, just declare and initialize the variable in the if:

void someFunc(Base * bp)
{
    if(const auto d1 = dynamic_cast<Derived1 *>(bp))
    {
        d1->func1();
    }
    else if(const auto d2 = dynamic_cast<Derived2 *>(bp))
    {
        d2->func2();
    }
    else
        cout << "None" << endl;
}

Note: d1 and d2 are constant pointers to mutable objects. Given we never modify them, I like to promise the compiler that we never modify them. That way the compiler can more easily reason about them for optimization, and I can more easily reason about them when I come to read the code in three months time.

2

Yes static_cast is ok. On the other hand, C++11 lets you write

if(auto d1 = dynamic_cast<Derived1*>(bp)) {
  d1-> func1();
} ...

so there is no need to have both.

Another option is to work with a C++17 std::variant (or the equivalent which is in Boost already) and avoid the if-elseif-elseif cascade, like they show on cppreference:

std::variant<Derived1*, Derived2*> p = new Derived1();
...
std::visit(overloaded(
  [](Derived1* p) { p->func1(); },
  [](Derived2* p) { p->func2(); }
), p);

If you can't use the common interface, this is the best option. Note, however, that if certain code is 'type-checking' like this to call a different function on the object, you may have missed something in your design.

xtofl
  • 40,723
  • 12
  • 105
  • 192
  • 2
    Actually, a declaration in the `if` statement was introduced in C++11 rather than C++14. (Of course, the OP might still be stuck on C++03 :-( ) – Martin Bonner supports Monica Nov 03 '17 at 08:22
  • And it goes without saying that this is bad practice, just like using result of regular assignment in condition... – user7860670 Nov 03 '17 at 08:26
  • 3
    @VTT: I agree with regular assignment - this is, however, declaring a variable in a condition. Do you have a reference for that statement? – xtofl Nov 03 '17 at 08:30
  • @xtofl I think this has been discussed for a very long time, ever since declaring variables inside of `for(;;)` became available breaking (or "extending" as some other people insist) existing scoping rules. – user7860670 Nov 03 '17 at 08:35
  • @VTT If the badness of the practice, even if held by a majority as such, doesn't induce some great chance of bugs (such as when used with parentheses), then it should probably be considered an opinion – Passer By Nov 03 '17 at 08:37
  • :) In that case, I regard _your_ opinion as ... opinionated. – xtofl Nov 03 '17 at 08:38
  • @xtofl You are free to say that my opinion even goes against [recommended "best practice"](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-cond). – user7860670 Nov 03 '17 at 08:46
  • @VTT "existing scoping rules" do you mean "all scopes begin with `{` and end with `}`"? rather than "A statement which contains sub-statements introduces a new scope" (which covers `for(;;)` et.al.) – Caleth Nov 03 '17 at 10:11
  • @Caleth: That's not how it works. `static_cast` can adjust the pointer too, so it does work for `Derived3` too. – geza Nov 03 '17 at 10:32
  • @Caleth I mean syntax originally used in C that prohibits declarations inside of parentheses in all situations. It is really surprising to see more and more fancy rules added considering how messy language syntax already is (and as a consequence, how poor language tooling is). – user7860670 Nov 03 '17 at 20:39
1

You can use static_cast to cast here but you could also leverage the dynamic_cast result to do the same thing.

void someFunc(Base * bp)
{
    if( Derived1 * d1 = dynamic_cast<Derived1 *>(bp)) 
    {
        d1->func1();
    }
    else if(Derived2 * d2 = dynamic_cast<Derived2 *>(bp))
    {
        d2->func2();
    }
    else
        cout << "None" << endl;
}
1

As alternative, you might use visitor pattern:

class Base;
class Derived1;
class Derived2;

class IBaseVisitor
{
public:
    virtual ~IBaseVisitor() = default;
    virtual void visit(Base&) = 0;
    virtual void visit(Derived1&) = 0;
    virtual void visit(Derived2&) = 0;
};

Then your classes with the accept method:

class Base
{
public:
    Base() { std::cout << "Base::Base" << std::endl; }
    virtual ~Base() { std::cout << "Base::~Base" << std::endl; }
    virtual void accept(IBaseVisitor& v) { v.visit(*this); }
};

class Derived1 : public Base
{
public:
    void func1() { std::cout << "Derived1::func1()" << std::endl; }
    void accept(IBaseVisitor& v) override { v.visit(*this); }
};

class Derived2 : public Base
{
public:
    void func2() { std::cout << "Derived2::func2()" << std::endl; }
    void accept(IBaseVisitor& v) override { v.visit(*this); }
};

Then the usage:

struct SomeFuncVisitor : IBaseVisitor
{
    void visit(Base&) override { std::cout << "None" << std::endl; }
    void visit(Derived1& d1) override { d1.func1(); }
    void visit(Derived2& d2) override { d2.func2(); }
};

int main()
{
    Derived1 derived1;
    Derived2 derived2;
    std::vector<Base *> vb {&derived1, &derived2};

    SomeFuncVisitor visitor;
    for (Base* base : vb) {
        base->accept(visitor);
    }
}

Demo

Jarod42
  • 203,559
  • 14
  • 181
  • 302