0

I have some template code which takes a shared pointer to a class and call a function or method. The problem comes up, if the called method is defined as const.

Example:

struct Y {}; 
struct X
{
        const Y Go() const { return Y{}; }
        const Y Go2() { return Y{}; }
};

Y f1( std::shared_ptr<X>  ) { return Y{}; }

template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
{
    return f( ptr, std::forward<ARGS>(args)... );
}

template < typename CLASS, typename RET, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, RET (CLASS::*mem_ptr)( ARGS...), ARGS&& ... args )->RET
{
    return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}

// Any chance to avoid the full duplication of the code here
// to define the member pointer to a const method?

template < typename CLASS, typename RET, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, RET (CLASS::*mem_ptr)( ARGS...) const, ARGS&& ... args )->RET
{
    return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}

int main()
{
    auto xptr = std::make_shared<X>();
    Y y1 = Do( xptr, &X::Go );
    Y y2 = Do( xptr, &X::Go2 );
    Y y3 = Do( xptr, &f1 );
}

My problem is the last specialization with the RET (CLASS::*mem_ptr)( ARGS...) const. I simply want to stop duplication the whole code only for the const. In real world code the function calls again another templated one, which results in duplicating a lot of code here.

Any chance to get rid of the specialization for the const member pointer?

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • @user463035818: Thats a minimal example and it it doesn't matter at all if the example code needs the functionality or not. The only purpose is to show that there is a need of the const specialized member pointer version... – Klaus Jan 14 '19 at 16:41
  • 1
    Related: https://stackoverflow.com/questions/24888597/template-wrapper-for-const-and-non-const-member-functions-of-arbitrary-classes – Holt Jan 14 '19 at 16:44
  • @Holt Not for this project, but I am also interested in a c++17 solution. It may come some time where our company where we use a c++17 compiler, maybe starting in 2025... ;) – Klaus Jan 14 '19 at 16:50
  • Side-note for C++17 and invoke - If `f1` took a `X&` instead of a shared-ptr, you could have used `std::invoke` for all three overload since `std::invoke(f, r, args... )` calls `f(r, args)` or `(r.*f)(args...)` depending on `f`. – Holt Jan 14 '19 at 17:27
  • Here is a different version that does not require SFINAE: https://godbolt.org/z/8pr6p6 – Holt Jan 14 '19 at 17:37

3 Answers3

4

In C++17, I would use a single templated function with a if constexpr and check if I can call f as a templated member function with std::is_invocable or not, and then use std::invoke to call it:

template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args ) {
    if constexpr (std::is_invocable_v<FUNC, decltype(ptr), ARGS...>) {
        return std::invoke(f, ptr, std::forward<ARGS>(args)... );
    }
    else {
        return std::invoke(f, ptr.get(), std::forward<ARGS>(args)... );
    }
}

Prior to C++17, you can have two overload: one for non-member functions and one for member functions. You can then use SFINAE to disable one or the other depending on the type of the callable (using something similar to std::is_invocable).

Holt
  • 36,600
  • 7
  • 92
  • 139
3

You might do:

template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
-> decltype((f(ptr, std::forward<ARGS>(args)... )))
{
    return f( ptr, std::forward<ARGS>(args)... );
}

template<typename MemberF, typename ... ARGS>
auto Do(std::shared_ptr<X>& base_ptr, MemberF mem_ptr, ARGS&& ... args)
-> decltype((base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...))
{
    return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • This does not seem to work with the first overload: https://godbolt.org/z/It-h0b Actually this does not work at all it looks like. – Holt Jan 14 '19 at 16:55
  • Great idea to match in the decltype clause for the return type to differentiate between pointer and member pointer. Thanks! You should also place the modified specialization for the function pointer to your answer as the link to godbolt may die and only the part you provided here will result in ambiguous overload. Thanks – Klaus Jan 14 '19 at 17:04
  • @Holt: thanks for correction. (trying to fix it with SFINAE on `std::is_member_function_pointer` on my side). – Jarod42 Jan 14 '19 at 17:06
1

Here is a C++14 version that does not require SFINAE and rely on the fact that:

  • const Y (X::*)() is the same as U1 X::* with U1 = const Y();
  • connt Y (X::*)() const is the same as U2 X::* with U2 = const Y() const.
template< typename FUNC, typename ... ARGS >
auto Do( std::shared_ptr<X>& ptr, FUNC&& f, ARGS&& ... args )
{
    return f( ptr, std::forward<ARGS>(args)... );
}

template < typename CLASS, typename U, typename ... ARGS>
auto Do( std::shared_ptr<X>& base_ptr, U CLASS::*mem_ptr, ARGS&& ... args )
{
    return (base_ptr.get()->*mem_ptr)( std::forward<ARGS>(args)...);
}

Posting a different answer because this is completely different than the first one, and both are interesting (in my opinion).

Holt
  • 36,600
  • 7
  • 92
  • 139
  • Quite interesting approach! Simply removing parms from pointer type and we see constness is kept... Wow! Tanks for that one! – Klaus Jan 14 '19 at 17:45