Call member function that can be reached by implicit conversion in C++

99 views Asked by At

Trying to create a reference wrapper similar to std::reference_wrapper, but with possibility to call the methods of the wrapped object.

#include <iostream>

class A {
public:
    void f() {
         std::cout << "A::f()\n";
    }
};

class WrapA {
public:
    WrapA(A& a) : _a_ptr{&a} {}

    operator A&() {
        return *_a_ptr;
    }
private:
    A* _a_ptr;
};

int main() {
    A a;
    WrapA wrap{a};

    wrap.f();                  // Fails with compile error: ‘class WrapA’ has no member named ‘f’
    static_cast<A>(wrap).f();  // Works

    return 0;
}

Why calling class A function f() on the wrapper object doesn't work, although implicit conversion from WrapA to A is possible? Is there some way to work around this issue?

Tried using std::reference_wrapper and it fails with same compile error.

2

There are 2 answers

2
NathanOliver On

One way to get this type of behavior is to overload the arrow operator operator -> and have it return a pointer to the wrapped object. That will get applied recursively by the compiler on the returned pointer and let you call the member function. That would give you

#include <iostream>

class A {
public:
    void f() {
         std::cout << "A::f()\n";
    }
};

class WrapA {
public:
    WrapA(A& a) : _a_ptr{&a} {}

    A* operator ->() {
        return _a_ptr;
    }
private:
    A* _a_ptr;
};

int main() {
    A a;
    WrapA wrap{a};

    wrap->f();
    
    return 0;
}

which outputs

A::f()

and you can see it working in this live example.

0
R Sahu On

You asked

Why calling class A function f() on the wrapper object doesn't work, although implicit conversion from WrapA to A is possible?

The standard specifies the contexts in which implicit conversion can take place (https://timsong-cpp.github.io/cppwp/n4140/conv#2):

2 [ Note: expressions with a given type will be implicitly converted to other types in several contexts:

(2.1) -- When used as operands of operators. The operator's requirements for its operands dictate the destination type (Clause [expr]).

(2.2) -- When used in the condition of an if statement or iteration statement ([stmt.select], [stmt.iter]). The destination type is bool.

(2.3) -- When used in the expression of a switch statement. The destination type is integral ([stmt.select]).

(2.4) -- When used as the source expression for an initialization (which includes use as an argument in a function call and use as the expression in a return statement). The type of the entity being initialized is (generally) the destination type. See [dcl.init], [dcl.init.ref].

end note ]

Please note that using an object in a dot operator is not one of them.