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 thatstd::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
orstd::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 usestd::move()
orstd::forward<T>()
when calling this constructor?- What would be the most general and type safe way to convert
A
toB
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 );
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.
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.
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).
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With