Search code examples
c++templatesc++11operator-overloadingperfect-forwarding

C++ method forwarding


I need to implement a class Container which acts exactly as the contained template class:

template <typename T>
class Container {
public:
      //...
private:
      // T data_;
};

T can be either a predefined type (e.g., int) or a user-defined type.

The purpose is to intercept any read/write operations done on the contained type.

I've succesfully implemented most operators, and it works.

However, when I need to access methods specific of the contained class T, it doesn't work:

Container<myclass> a;
a.myclass_specific_method();

The reason is that Container obviously doesn't have such methods. Moreover, since T is a template, its methods cannot be known in advance.

I guess there is no solution to this problem, even with C++11, because operator . cannot be overloaded. Therefore, the only possible approach is to always rely on operator-> like smart pointers do.

Can you confirm ?


Solution

  • For a class type T, this will act a lot like a T:

    template<class T, class=void>
    struct Container : public T { // inheritance MUST be public
      using T::T;
      Container() = default; // or override
      Container( Container const& ) = default; // or override
      Container( Container && ) = default; // or override
      Container& operator=( Container const& ) = default; // or override
      Container& operator=( Container && ) = default; // or override
      // here, we override any method we want to intercept
    
      // these are used by operators:
      friend T& get_t(Container& self){return self;}
      friend T const& get_t(Container const& self){return self;}
      friend T&& get_t(Container&& self){return std::move(self);}
      friend T const&& get_t(Container const&& self){return std::move(self);}
    };
    

    for a non-class T, we detect it and use a different implementation:

    template<class T>
    struct Container<T, typename std::enable_if<!std::is_class<T>{}>::type > {
      T t;
      Container() = default; // or override
      Container( Container const& ) = default; // or override
      Container( Container && ) = default; // or override
      Container& operator=( Container const& ) = default; // or override
      Container& operator=( Container && ) = default; // or override
    
      // these are used by operators:
      friend T& get_t(Container& self){return self.t;}
      friend T const& get_t(Container const& self){return self.t;}
      friend T&& get_t(Container&& self){return std::move(self).t;}
      friend T const&& get_t(Container const&& self){return std::move(self).t;}
    };
    

    finally, we go off and override every operator we can find in a SFINAE friendly way, where the operator only participates in overload resolution if get_t(Container) would work in its place in the operator. This should all be done in a namespace, so the operators are found via ADL. An overload of get_t that returns its argument unchanged could be useful to massively reduce the number of overloads.

    This could be another 100 or more lines of code.

    Users of Container<T> can bypass the Container<T> and get the underlying T in the above system.