Trying to understand std::forward, std::move a little better
Asked Answered
D

1

9

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 );
Deirdra answered 26/1, 2018 at 1:27 Comment(18)
T&& is a forwarding-reference only if template type deduction is in play. From your code Converter<int, float> converter(i, f), there's no type deduction happening, so the two parameters end up being rvalue-references.Garderobe
@MárioFeroldi Okay that gives a little bit of clarity and does make sense, but I'm still trying to figure out how to achieve the ability of not having to use or call std::move, std::forward when calling the constructor in general.Deirdra
std::move is the better option for this case. The purpose of std::forward is to be used with a deduced template parameter argumentDoucet
@M.M. hmm okay that seems to make a bit of sense; so as long as this class's template declaration doesn't have template parameter arguments then forward is overkill and move is suitable... I think I'm starting to get the general concept of the overall differences between the two.Deirdra
They both compile to the same thing (when T is not an lvalue reference), the issue is about documenting intentDoucet
To those who have responded with valid, vital, and clarifying arguments: I'm in the process of learning these techniques by practice. I could of easily just written a function template but decided to go with a class template instead for future reasons. I might want to add functionality say for reporting, statistical purposes, etc. ... Yes as this class is currently using basic types I do understand what it is that you are saying. However, lets say that later I have a vector<int>{1,2,3,...} as the From and want the To to be set<int>{1,2,3...} etc. ...?Deirdra
@MárioFeroldi I have made an edit to my question.Deirdra
@Doucet I have made an edit to my question.Deirdra
This looks like reinventing std::transform ?Brachiate
Reading books like Discovering Modern C++ by Peter Gottschling and Effective Modern C++ by Scott Meyers would be a good idea. In my opinion, this is one of the best way to get a good overview of modern C++.Langdon
@Langdon I appreciate the recommendation of excellent books or reading material however at the current time; I'm limited on resources and don't have a means to retrieve a copy. I currently learn what I know by 3 main methods: trial and error, reading online documents & tutorials, and asking and answer questions here on stack.Deirdra
The above code does not makes much sense. If you do a conversion class, then you don't want to create a dummy object nor to do multiple copies of the object.Langdon
Why are the constructors private? Where is _to defined? Please post a "nearly compilable" complete program.Appetency
@JiveDadson oh good point; they're supposed to be public; bad typo thank you for pointing that out.Deirdra
Fixed a typo I forgot to add the public: keyword in the class declaration. Thanks to user: JiveDadson for pointing it out. I do have them public in my IDE.Deirdra
@Langdon I'm only using objects within the class for testing purpose; in the finished class, there really shouldn't be any as it should have perfect forwarding. I might change these to be a reference of the objects passed in so that the class itself can reference them for other in class member functions.Deirdra
@JiveDadson I also fixed the typo at the end of the class to_ it was supposed to be out_. Thank you for pointing that out. I was in the process of changing some of the variable names - identifiers while asking - writing this question. I was trying to have the code make clear sense of what was trying to be achieved. 90% of the time I will copy code right from my IDE to here, but every now & then I'll just type it out when then a classes are very simple or small.Deirdra
Fixed another typo pointed out by JiveDadson at the end of the class the pseudo operator()() was returning to_ its supposed to be out_. When originally asking - writing this question I did decide to change a few of the variable names to make the reading of it a little clearer. Originally in my code for the member variables I did have From from_ and To to_ same thing as in the constructor, From from & To to which is okay, but when reading it from a verbal sense it sounded redundant in my mind so I changed them to what they now are. My apologies for any inconvenience.Deirdra
K
4

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

Kriss answered 20/3, 2018 at 1:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.