Reputation: 7905
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 );
Upvotes: 9
Views: 1320
Reputation: 136
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
Upvotes: 4