Make a code “forwarding referencable”

混江龙づ霸主 提交于 2019-12-13 19:07:48

问题


I opened this post about forwarding reference, this is a (hopefully) MCVE code:

#include <functional>
#include <vector>

using namespace std;
struct MultiMemoizator {
    template <typename ReturnType, typename... Args>
    ReturnType callFunction(std::function<ReturnType(Args...)> memFunc, Args&&... args) {

    }
};

typedef vector<double> vecD;

vecD sort_vec (const vecD& vec) {
    return vec;
}

int main()
{
    vecD vec;
    std::function<vecD(const vecD&)> sortFunc(sort_vec);
    MultiMemoizator mem;
    mem.callFunction<vecD, vecD>(sortFunc, vec);
}

Since this is not the whole code, maybe I'll have to add extra code based on the answers.

Anyway, as was suggested in this answer, forwarding reference is not possible with this version, since Args is not deduced.

So my question is: is it possible to make this code "forwarding referencable"?


回答1:


In order to perfect-forward your arguments, you need to have the types deduced. You can do this by deducing the arguments to the function and the parameters to the functor separately:

template <typename ReturnType, typename... FunArgs, typename... Args>
ReturnType callFunction(std::function<ReturnType(FunArgs...)> memFunc,
                        Args&&... args) 
{
    //...
}

Then you can call callFunction without template parameters and have everything deduced:

mem.callFunction(sortFunc, vec);



回答2:


I will add a bit of details regarding @TartanLlama answer on why your code fails to compile (even without the explicit template parameters) but also why (in my own opinion) your code is dangerous.

In the following, I will use only a simple type T instead of your parameter pack Args... because it is simpler to explain and does not change the meaning.

A bit of reminder on forwarding references...

First, let's take a simpler example than yours with simply the following:

template <typename T>
void f (T&&);

Now, let's instanciate f from various sources, let's assume with have the following variables:

std::string s;
const std::string cs;

...then:

f(s); // instanciate f<std::string&>
f(cs); // instanciate f<const std::string&>
f(std::string()); // instanciate f<std::string&&>

You should be wondering: Why is the first instanciation f<std::string&> instead of f<std::string>?, but the standard tells you (§14.8.2.1#3 [temp.deduct.call]):

If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Back to our initial snippet!

Now, let's complicate a bit our example:

template <typename T> 
struct A {};

template <typename T>
void f (A<T>, T&&);

And one instantiation:

std::string s;
A<std::string> as;
f(as, s);

The above is equivalent to your example, and will fails to compile, but why... ? Well, as explained above, when you have an lvalue, the deduced type for T&& is T&, not T, and thus the type deduction fails for A<T> because the compiler is expecting A<std::string&> and you are giving a A<std::string>.

So now we know that we have to do the following:

A<std::string&> ars;
A<std::string const&> acrs;
f(ars, s); // good
f(acrs, cs); // good

Why is it dangerous?

Ok so now, this should be good:

A<std::string&&> arrs;
f(arrs, std::string());

But it is not... Because when T is deduced as a rvalue reference, T is simply T, so the compiler is expecting A<std::string>.

So here is the problem: You are going to give a rvalue to a method that is going to forward it to a function expecting an lvalue. That's not wrong, but it is probably not what you'd have expected.

How to deal with it?

The first possibility is to force the type of the first parameter regardless of the deduced type for T, e.g.:

template <typename T>
void f (A<typename std::remove_reference<T>::type>, T&&);

But note:

  1. You would have to add more stuff to deal with const.
  2. One may wonder the usefulness of T&& when the type of the first argument is fixed (in your case, at least).

The second possibility (warning: I don't know if this is standard!) is to move the first parameter at the end and then deduce the type from t:

template <typename T>
void f (T &&t, A<decltype(std::forward<T>(t))>);

Now you have an exact match between the deduced type for T and the expected type for A.

Unfortunately I don't know how to make the above work with variadic templates...



来源:https://stackoverflow.com/questions/36843699/make-a-code-forwarding-referencable

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!