Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to understand std::forward, std::move a little better

I'm in the process of writing a basic class template. It takes two argument types for its parameters. The idea of the class is to take one type in as a const ref and the other as a ref. The functionality of the class is to convert type A to type B where the object being created will end up being b. I would like to have perfect-forwarding or move semantics a valid part of this class template.


For now here is my current class with just basic types, but plan to expand this to any 2 types with the use of a variadic construction.

#ifndef CONVERTER_H
#define CONVERTER_H

#include <utility>

template<class From, class To>
class Converter {
private:
    From in_;
    To   out_;

public:
    // Would like for From in to be a const (non modifiable) object
    // passed in by perfect forwarding or move semantics and for 
    // To out to be returned by reference with perfect forwarding 
    // or move semantics. Possible Constructor Declarations - Definitions

    // Using std::move
    Converter( From&& in, To&& out ) :
        in_{ std::move( in ) },
        out_{ std::move( out ) } 
    {
        // Code to convert in to out
    }

    // Or using std::forward
    Converter( From&& in, To&& out ) :
        in_{ std::forward<From>( in ) },
        out_{ std::forward<To>( out ) } {

        // Code to convert in to out.
     }        

    // Pseudo operator()... 
    To operator()() {
        return out_;
    }
};

#endif // !CONVERTER_H

In either way that I declare the constructor(s) above with std::move or std::forward this class compiles on its own. Now when I include this and try to instantiate an object above calling its constructor(s)... if I do this:

int i = 10;
float f = 0;  
Converter<int, float> converter( i, f );

This gives me a compiler error in Visual Studio 2017 in both cases.

1>------ Build started: Project: ExceptionManager, Configuration: Debug Win32 ------
1>main.cpp
1>c:\users\skilz80\documents\visual studio 2017\projects\exceptionmanager\exceptionmanager\main.cpp(54): error C2664: 'Converter<unsigned int,float>::Converter(Converter<unsigned int,float> &&)': cannot convert argument 1 from 'unsigned int' to 'unsigned int &&'
1>c:\users\skilz80\documents\visual studio 2017\projects\exceptionmanager\exceptionmanager\main.cpp(54): note: You cannot bind an lvalue to an rvalue reference
1>Done building project "ExceptionManager.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Which is understandable enough {can't bind lvaule to rvalue ref}.


However if I try to use the constructor(s) like this:

int i = 10;
float f = 0;
Converter<int,float> converter( std::move( i ), std::move( f ) );

// Or
Converter<int,float> converter( std::forward<int>( i ), std::forward<float>( f ) );

It compiles and builds regardless if std::move(...) or std::forward<T>(...) is used within the class.


To my better understanding it apparently seems that std::move(...) & std::forward<T>(...) are nearly interchangeable and do the same thing except that std::forward<T>(...) has the extra cast involved.


Right now as this class stands since I'm only showing basic types it would seem more plausible to use std::move, however I may eventually want to use more complex types so I would like to design this ahead of time with that thought in mind, so I'm leaning towards std::forward<T> for the perfect forwarding.


With this being said and to complete this class there are 3 questions coupled together.

  • If I'm using either std::move or std::forward in the class's constructor's member initialize list, why would I have to use them again when instantiating the template class object; wouldn't this be considered redundant? If so how would the constructor look so that the user wouldn't have to use std::move() or std::forward<T>() when calling this constructor?
  • What would be the most general and type safe way to convert A to B within this context?
  • Once the above two are answered with clarity then this last part within this context with the above mentioned standard classes or other similar types would then be what in regards to implementing the operator()() and how would it look with respect to what has already been mentioned above?

To finish things up concerning the 3 coupled questions above my final thought here is that at one point in time of the design process I had thought about using std::any and its related functions as a possible part of its implementation process. I don't know if std::any could be used in this context or not and if so how?


EDIT

Here might be a couple of plausible possible future uses of this class:

vector<int> vecFrom{1,2,3, ...};
set<int>    setTo;

Converter<vector<int>, set<int>> converter( vecFrom, setTo );

Or after expanding...

vector<int>     vecIntFrom{1,2,3, ...};
vector<string>  vecStringFrom{ "a", "b", "c", ... };
map<int,string> mapTo;
Converter<vector<int>, vector<string>, map<int,string> converter( vecIntFrom, vecStringFrom, mapTo );
like image 611
Francis Cugler Avatar asked Jan 26 '18 01:01

Francis Cugler


People also ask

What is the difference between std :: forward and std :: move?

std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.

What is the purpose of std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Should you use std :: move?

Q: When should it be used? A: You should use std::move if you want to call functions that support move semantics with an argument which is not an rvalue (temporary expression).

What happens to object after std :: move?

std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.


1 Answers

There are a lot of questions here that don't seem concisely answerable to me, but one stands out:

"What is the difference between std::move and std::forward?"

std::move is used to convert an lvalue reference to an rvalue reference, often to transfer the resources held by one lvalue to another.

std::forward is used to distinguish between lvalue and rvalue references, often in the case where a parameter of type rvalue reference is deduced to be an lvalue.

The upshot: If you want to branch based on what kind of reference an object was when passed, use std::forward. If you just want to pilfer the resources of an lvalue by means of converting to an rvalue, use std::move.

For more details, I found the following helpful: http://thbecker.net/articles/rvalue_references/section_01.html

like image 99
Dr. Watson Avatar answered Sep 28 '22 04:09

Dr. Watson