0

I want to make a templated class which holds an instance of another class and forwards one of its foo methods with the correct argument type. Is there a clever meta-programming way of doing a "perfect forwarding" of the inner method?

template <typename Inner>
class Outer {
private:
  Inner inner;

public:

  // To-do: replicate foo method of Inner with identical signature,
  // how to pick correct T?
  void foo(T arg) { inner.foo(arg); }

};

I can see two classic solutions, but is there a better modern one with meta-programming?

  • Inheritance with partially protected interface: Outer could publicly inherit from Inner. But Inner also has methods which are only to be called by Outer and not the user. Those can be protected, ok, but it also tightly couples the implementations of Outer and all types of Inner classes. The public interface of Outer can be arbitrarily extended by public methods in Inner, which is not desired.
  • Make foo a templated function template <typename T> void foo(T&& arg) { inner.foo(std::forward<T>(arg)); }. This is perfect forwarding of the argument, but if a user calls foo with the wrong argument, the error reports Inner::foo instead of Outer::foo. This breaks the encapsulation provided by the public interface of Outer.
olq_plo
  • 1,002
  • 10
  • 18
  • 2
    I can't test it right now, but it should be possible using something like described here [get type of first, second etc argument](https://stackoverflow.com/questions/27879815/c11-get-type-of-first-second-etc-argument-similar-to-result-of) – t.niese Aug 14 '18 at 13:02
  • True, I guess mine is a duplicate. The answer with `using` is also nice and simple. – olq_plo Aug 14 '18 at 13:38

2 Answers2

4

Something along these lines perhaps:

template <typename Inner>
class Outer : private Inner {
public:
  using Inner::foo;
};

Demo

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • I think OP's first point is the same as this answer. – apple apple Aug 14 '18 at 13:11
  • 3
    @appleapple The OP was concerned about accidentally exposing methods other than `foo` off `Outer`. This technique doesn't so expose. – Igor Tandetnik Aug 14 '18 at 13:12
  • Might want to note that if `foo` is overloaded it will expose all overloads. I'm not sure if the OP cares about that but someone might. – NathanOliver Aug 14 '18 at 13:16
  • Cool, yes, this is an acceptable solution. And pretty straight-forward. I will wait a bit more, but I guess this is going to be the accepted answer, even if it does not use meta-programming :) – olq_plo Aug 14 '18 at 13:24
0

Here's one way which gives almost perfect error messages:

#include <string>

// a type which yields the type we gave it
template<class T> struct passer 
{ 
    using type = T; 
};

// an easy-to-use alias
template<class T> using pass_t = typename passer<T>::type;

// example
template <typename Inner>
class Outer {
private:
  Inner inner;

public:

  // To-do: replicate foo method of Inner with identical signature,
  // how to pick correct T?
  // Ans: with a pass_t
    template<class T>
    auto foo(T&& arg) 
    -> pass_t<decltype(this->inner.foo(std::forward<T>(arg)))>
    {
       return inner.foo(std::forward<T>(arg)); 
    }

};


struct Bar
{
    void foo(std::string const& thing);
};

struct Baz
{
    int foo(int thing) { return thing * 2; };
};

int main()
{

    auto o = Outer<Bar>();
    o.foo(std::string("hi"));
    o.foo("hi");

    int i = 1;

    /* - uncomment for error
    o.foo(i);

    note the nice error message on gcc:
        <source>:41:7: error: no matching member function for call to 'foo'
        <source>:19:10: note: candidate template ignored: substitution failure [with T = int]: reference to type 'const std::string' (aka 'const basic_string<char>') could not bind to an lvalue of type 'int'
    */

// same here:
//    o.foo(1);

// but this is fine
    auto o2 = Outer<Baz>();
    auto x = o2.foo(2);

    // and this is not
    // note: candidate template ignored: substitution failure [with T = char const (&)[6]]: cannot initialize a parameter of type 'int' with an lvalue of type 'char const[6]'
//    auto y = o2.foo("dfghj");

}

Link here: https://godbolt.org/g/UvsrbP

Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • This is a very insightful answer, thank you. I prefer the answer with `using Inner::foo`, even though it does not use meta-programming, but it solves the issue I had with public inheritance. – olq_plo Aug 14 '18 at 13:29
  • @olq_plo Oh, I thought you wanted a forwarding solution for encapsulation. Never mind, I'll leave this here in case it's of use to someone. – Richard Hodges Aug 14 '18 at 13:44
  • It turns out that Boost.CallableTraits can do what I want, more specifically the `args_t` meta-function. https://www.boost.org/doc/libs/1_68_0/libs/callable_traits/doc/html/index.html – olq_plo Aug 14 '18 at 13:51