Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Template function does not work for pointer-to-member-function taking const ref

Tags:

c++

templates

Lately I wrote a template function to solve some code repetitions. It looks like this:

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, const std::string& error, R (T::*fun)(Args...), Args... args) {
    if (auto sp = ptr.lock()) 
    {
        return std::invoke(fun, *sp, args...);
    }
    else 
    {
        throw std::runtime_error(error.c_str());
    }
}

int main() {
    auto a = std::make_shared<A>();
    call_or_throw(std::weak_ptr<A>(a), "err", &A::foo, 1);
}

This code works perfectly well for class A which looks like this:

class A {
public:
    void foo(int x) {

    }
};

But fails to compile for one like this:

class A {
public:
    void foo(const int& x) {

    }
};

Why is it so (by why I mean why it fails to deduce the type) and how (if it is possible at all) can I make this code work with references? Live example

like image 752
bartop Avatar asked Oct 23 '19 10:10

bartop


People also ask

Can template be a pointer?

A template has only one type, but a specialization is needed for pointer, reference, pointer to member, or function pointer types. The specialization itself is still a template on the type pointed to or referenced.

What is template argument deduction in c++?

Class Template Argument Deduction (CTAD) is a C++17 Core Language feature that reduces code verbosity. C++17's Standard Library also supports CTAD, so after upgrading your toolset, you can take advantage of this new feature when using STL types like std::pair and std::vector.

Can member functions be declared as template?

Member functions can be function templates in several contexts. All functions of class templates are generic but are not referred to as member templates or member function templates. If these member functions take their own template arguments, they are considered to be member function templates.


2 Answers

Args types cannot be deduced both as const& (from fun parameter declaration) and non-reference from args declaration. A simple fix is to use two separate template type parameter packs:

template<class T, class R, class... Args, class... DeclaredArgs>
R call_or_throw(
    const std::weak_ptr<T>& ptr,
    const std::string& error,
    R (T::*fun)(DeclaredArgs...),
    Args... args);

As a downside, I can imagine slightly longer error messages in case of bad usage.

like image 133
LogicStuff Avatar answered Sep 30 '22 02:09

LogicStuff


Note that the template parameter Args's type is deduced as const int& on the 3rd function argument &A::foo, and deduced as int on the 4th function parameter 1. They don't match and cause deduction fails.

You can exclude the 4th parameter from deduction, e.g.

template<class T, class R, class... Args>
R call_or_throw(const std::weak_ptr<T>& ptr, 
                const std::string& error, 
                R (T::*fun)(Args...), 
                std::type_identity_t<Args>... args) {
//              ^^^^^^^^^^^^^^^^^^^^^^^^^^                

LIVE

PS: std::type_identity is supported since C++20; but it's quite easy to implement one.

like image 29
songyuanyao Avatar answered Sep 30 '22 04:09

songyuanyao