1

Is it possible to write template function in C++14 like below

Here is the sample https://godbolt.org/z/9gRk-t

// pseudo code

#include <type_traits>

template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args) 
{
  return (obj.*mf)(std::forward<Args>(args)...);
}

So, for a test class

struct Test 
{
  int Func(){return 1;};
  bool Func(bool){return true;};  // overload

  void FuncInt(int){};
};

The template coudl work for the use case below (but it failed)

int main()
{
  Test test;

  // for overload case
  auto a = Call<int()>(test, &Test::Func);
  auto b = Call<bool(bool)>(test, &Test::Func, true);

  // for non-overload case
  Call(test, &Test::FuncInt, 1);

  return 0;
}

Herer the erro info.

#1 with x64 msvc v19.24
example.cpp
<source>(23): error C2672: 'Call': no matching overloaded function found
<source>(23): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
<source>(24): error C2672: 'Call': no matching overloaded function found
<source>(24): error C2770: invalid explicit template argument(s) for 'decltype(auto) Call(T &,R (__cdecl T::* )(Args...),Args &&...)'
<source>(5): note: see declaration of 'Call'
Compiler returned: 2
5
  • Syntax errors in Test member definitions: redundant semicolons after method definitions, missing semicolon after return statements and struct's closing brace. Commented Mar 28, 2020 at 7:00
  • Notice there is std::invoke in C++17 Commented Mar 28, 2020 at 10:34
  • @Jarod42 Although you can't directly pass a pointer to overloaded member function to std::invoke. Commented Mar 28, 2020 at 15:59
  • @aschepler: std::invoke(static_cast<bool (Test::*)(bool)>(&Test::Func), test, true); or std::invoke([](Test& test, auto... args){ return test.Func(args...);}, test, true);. Commented Mar 28, 2020 at 19:30
  • @Jarod Which is why I made sure to type "directly" ;) Commented Mar 28, 2020 at 22:06

3 Answers 3

3

Issue with

template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args)

is that Args is deduced twice and should be identical.

There are several ways to fix that issue:

  • Add extra template parameter:

    template <typename T, typename R, typename... Args, typename ... Ts>
    decltype(auto) Call(T& obj, R(T::*mf)(Args...), Ts&&... args)
    {
        return (obj.*mf)(std::forward<Ts>(args)...);
    }
    

    Demo

  • or make parameter non deducible (I use std::type_identity from C++20 but can trivially be reimplemented in previous version):

    template <typename T, typename R, typename... Args>
    decltype(auto) Call(T& obj, R(T::*mf)(Args...), std::type_identity_t<Args>... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
    
  • or change completely signature:

    template <typename T, typename M, typename... Args>
    decltype(auto) Call(T& obj, M mf, Args&&... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
    

    Demo

Sign up to request clarification or add additional context in comments.

2 Comments

The 1st solution doesn't work for overloaded case. godbolt.org/z/joUTQw The 3rd solution doesn't work for all case. godbolt.org/z/D8g-Kq I call them in a wrong way?
@foo: The way you select overload is wrong, fixed typo in last solution. Demo added
1

In your declaration of Call:

template <typename T, typename R, typename... Args>
decltype(auto) Call(T& obj, R(T::*mf)(Args...), Args&&... args);

the function template takes (or might try to deduce) two or more template arguments: the first is T, the second is R, and the rest are Args. So giving a single function type as the first template argument as in Call<int()> and Call<bool(bool)> is wrong. The correct way to call it would be

auto a = Call<Test, int>(test, &Test::Func);
auto b = Call<Test, bool, bool>(test, &Test::Func, true);

Another issue is that if you want the template arguments deduced, as in the non-overloaded case, since the Args pack appears twice, it will only work if the lists deduced from the member function and from the trailing arguments are exactly the same:

int n = 3;
Call(test, &Test::FuncInt, n); // error!
// Args... is deduced to `int` from `&Test::FuncInt`, but deduced to `int&`
// from `n` since it's an lvalue matching a forwarding reference parameter.

If you prefer the function type syntax, you could use the solution of @foo:

template <typename FuncT, typename T, typename... Args>
constexpr decltype(auto) Call(T& obj, FuncT T::*mf, Args&&... args)
    noexcept(noexcept((obj.*mf)(std::forward<Args>(args)...)))
{
    return (obj.*mf)(std::forward<Args>(args)...);
}

// main() exactly as in question, including Call<int()> and Call<bool(bool)>.

FuncT T::*mf is the syntax for declaring a pointer-to-member, which is often used to point to a data member, but also works to point to a function if the type FuncT is a function type. (I've added the constexpr and conditional exception specifier to make it more generic.)

This also solves an issue with the original, which can't be used to invoke a member function which is const or which has a ref-qualifier, since this creates a different function type:

class Test2 {
public:
    int get() const;
    void set() &;
};

void driver_Test2() {
    Test2 test;

    // Error with original Call:
    // Type of &Test2::get is "int (Test2::*)() const",
    // which cannot match "R (Test2::*)(Args...)"
    int a = Call(test, &Test2::get);

    // Error with original Call:
    // Type of &Test2::set is "void (Test2::*)(int) &",
    // which cannot match "R (Test2::*)(Args...)"
    Call(test, &Test2::set, 1);
}

But with the new Call definition, driver_Test2 is fine, since any non-static member function can match FuncT T::*. If we wanted to supply a template argument to the calls in driver_Test2, maybe because the member functions are overloaded, that would look like Call<int() const> and Call<void() &>.

Comments

0

I have figured it out. It is quietly like std::men_fn.

Here it is the code below and sample https://godbolt.org/z/NoWPV_


#include <type_traits>

template<typename T>
struct Proxy
{
    template<typename R, typename ...Args>
    decltype(auto) Call(T& obj, R T::*mf, Args&&... args)
    {
        return (obj.*mf)(std::forward<Args>(args)...);
    }
};

struct Test 
{
  int Func(){return 1;};
  bool Func(bool){return true;};  // overload

  void FuncInt(int){};
};

int main()
{
  Test test;
  Proxy<Test> proxy;

  // for overload case
  auto a = proxy.Call<int()>(test, &Test::Func);
  auto b = proxy.Call<bool(bool)>(test, &Test::Func, true);

  // for non-overload case
  proxy.Call(test, &Test::FuncInt, 1);

  return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.