I have made simple test to understand better move semantics. Output result was unexpected for me. Bellow is my test function:
template<class T>
void test(T&& v)
{
v++;
}
void main()
{
int v = 1;
test(std::move(v));
std::cout << "Output:" << v << std::endl;
}
My expectation was:
Output: 1
But real result was:
Output: 2
I thought as follows - I use std::move(v), as result I have conversion to "rvalue" and test function will work with temporal variable. Therefore the result should be Output: 1.
What is wrong in my conclusions?
std::move never constructs a temporary. It is a conversion to an rvalue-reference.
This reference is bound to main::v, then test::v parameter is initialized with it, and thus also bound to main::v.
If you want to create a temporary, use a cast:
test(int{v});
To help you understand what type your template deduced, you can use the macro __PRETTY_FUNCTION__
template<class T>
void test(T&& v)
{
std::cout<< __PRETTY_FUNCTION__ << std::endl;
v++;
}
Also this guys explains a lot of things about template deduction (your case at 35')
https://www.youtube.com/watch?v=vwrXHznaYLA
Related
I'm working out an "Any" class by myself. As following code shown, I have two questions.
#include <assert.h>
#include <iostream>
#include <typeinfo>
class Test{};
class Any {
public:
template<typename DataType>
explicit Any(DataType&& in) {
Test t;
std::cout
<< typeid(t).name() << " "
<< typeid(in).name() << " "
<< typeid(Test()).name();
std::cout << " move";
}
template<typename DataType>
explicit Any(const DataType& in) {
Test t;
std::cout
<< typeid(t).name() << " "
<< typeid(in).name() << " "
<< typeid(Test()).name();
std::cout << " copy";
}
};
int main()
{
Test t;
Any a(t);
}
Compilation command is
g++ main.cpp -std=c++11
The output is
4Test 4Test F4TestvE move
Why does c++ choose move construct rather than copy construct? "t" is an instance of Test which it's not a rvalue.
Why typeid(in) and typeid(Test()) is not same? They are both rvalue.
Thanks a lot.
Why does c++ choose move construct rather than copy construct? "t" is an instance of Test which it's not a rvalue.
The 1st constructor overload takes forwarding reference and could accept both lvalues and rvalues. (Hence it's not move constructor.) For Any a(t); it's an exact match, while for the 2nd overload t needs to be converted to const.
Why typeid(in) and typeid(Test()) is not same? They are both rvalue.
Test() is a function type, which returns Test and takes nothing, thus typeid(Test()) gives different result.
The reason your move variant is invoked is because its signature doesn't actually require an rvalue reference to be passed.
If U = T const& then U&& will be a T const&, not a T&&. In your case DataType binds to Test&, not, as you likely expected, to Test. The && therefore collapses and does nothing. You can see this in action if you add a static_assert(std::is_same_v<DataType, Test&>); which will pass.
You function will be called if you replace the template with a fixed Type, i.e. Test&& instead of DataType&&.
Have a look at reference, section Reference collapsing.
I'm puzzled about the boost::make_optional() behavior when used with the template specification.
In particular, it's still unclear to me why this:
int pizza = 5;
boost::optional<int> pizza_opt = boost::make_optional<int>(pizza)
throws the compile error cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int’; while this:
int foo(int bar)
{ return bar; }
boost::optional<int> pizza_opt = boost::make_optional<int>(foo(pizza))
works fine.
I already know from this that it does not make much sense to use boost::make_optional specifying the type, but I'm reading some code which does use of this structure.
Thank you!
Template parameter of boost::make_optional doesn't define exactly type inside optional.
This template parameter is responsible for perfect forwarding, here is simple minimal example reproducing issue:
#include <iostream>
template<typename T>
void bar(T&& x)
{
std::cout << __PRETTY_FUNCTION__ << " "
<< std::forward<T>(x) << '\n';
}
int foo(int x)
{
return x + 1;
}
int main()
{
int pizza = 5;
bar(pizza);
bar<int>(foo(pizza));
// bar<int>(pizza); // same error
return 0;
}
Live demo.
So when deduction is done T is int& for l-values and int for r-values.
When you pass variable you passing l-value.
When you specified type you are forcing argument to be int && which do not match to int&.
I'm trying to write a wrapper around shared_ptr that can implicitly dereference to underlying type. The code is as below:
#include <memory>
template<typename T>
class PtrWrapper {
public:
PtrWrapper(std::shared_ptr<T> ptr) : ptr_(ptr) {}
operator T& () {
return *ptr_;
}
T& ref() {
return *ptr_;
}
private:
std::shared_ptr<T> ptr_;
};
Looks like that there's nothing wrong with it. I tried a few methods to use the wrapper:
#include <iostream>
class Nothing {
public:
Nothing() {
std::cout << "Construct " << this << std::endl;
}
Nothing(Nothing const& parent) {
std::cout << "Copy " << &parent << " " << this << std::endl;
}
Nothing(Nothing && parent) {
std::cout << "Move " << &parent << " " << this << std::endl;
}
~Nothing() {
std::cout << "Destruct " << this << std::endl;
}
};
int main() {
PtrWrapper<Nothing> wrapper{std::make_shared<Nothing>()};
// #1: OK
Nothing & by_assignment = wrapper;
// #2: OK
Nothing & by_operator{wrapper.operator Nothing &()};
// #3: OK
Nothing & by_function{wrapper.ref()};
// #4: OK
Nothing & by_initialization(wrapper);
// #5: Compile error: non-const lvalue reference to type 'Nothing' cannot bind to an initializer list temporary
// Nothing & by_initialization_2{wrapper};
// #6: The `Nothing` class is copied, which is not expected
Nothing const& by_initialization_3{wrapper};
return 0;
}
The wrapper class works well with assignment and parentheses initialization.
The weird thing is, when I'm trying to initialize Nothing& with initializer list (#5 and #6 in the code above), the value is copied and I must use a const reference to it. However, when I explicitly call the conversion operator like wrapper.operator Nothing &() (#2 in code above), I got the correct reference to the original object constructed at the first line.
I've read cppreference and found that initializer list is a copy-initialized temporary list, but it doesn't make sense why the code works when operator Nothing &() is explicitly called.
Anyone can help me with figuring out what is happening here? Thanks very much!
You are actually doing reference initialization here:
Nothing & by_initialization_2{wrapper};
The rules say that since the initializer is not the same type as the reference being bound, user-defined conversion operators are considered, which is fine, since you have the appropriate conversion operator.
However, if the l-value returned by the conversion function is passed through a brace-init list, then a temporary is materialized. Since you can't bind a non-const reference to a temporary, the initialization fails.
#include <iostream>
#include <utility>
template<typename T>
void f1(T&& t) // &&
{
if constexpr (std::is_function_v<typename std::remove_pointer_t<T>>)
std::cout << "function" << std::endl;
else
std::cout << "not a function" << std::endl;
}
template<typename T>
void f2(T& t) // &
{
if constexpr (std::is_function_v<typename std::remove_pointer_t<T>>)
std::cout << "function" << std::endl;
else
std::cout << "not a function" << std::endl;
}
void print(){}
int main()
{
f1(print);
f2(print);
return 0;
}
According to f1, print is not a function.
According to f2, print is a function.
Understanding why this is so would help understanding the && operator
In the both cases a function is passed by reference. And the both functions deals with lvalue reference to the function print.
Use
std::is_function_v<std::remove_reference_t<T>>
instead of
std::is_function_v<typename std::remove_pointer_t<T>>
You can also insert a statement like this in the both functions
std::cout << std::is_lvalue_reference_v<decltype( t )> << '\n';
to be sure that the functions deal with the lvalue reference to rpint.
Take into account that you need to include the header <type_traits>.
If you want that the functions would deal with function pointers then you need to use a call like this
f1(&print);
In this case the second function should be declared like
template<typename T>
void f2( const T& t);
Otherwise you may not bind a non-constant reference to rvalue.
Or call the function f2 like if you do not want to use the qualifier const.
auto p = print;
f2(p);
Following is the code brought from this question.
// [I]
void overloaded_function(const std::string& param) {
std::cout << "const std::string& version: " << param << '\n';
}
// [II]
void overloaded_function(std::string&& param) {
std::cout << "std::string&& version: " << param << '\n';
}
template <typename T>
void pass_through_f(T&& param) {
overloaded_function(std::forward<T>(param));
}
template <typename T>
void pass_through(T&& param) {
overloaded_function(param);
}
int main() {
std::string str = "Hello World";
pass_through_f(str); // (1)
pass_through_f(std::move(str)); // (2)
std::cout << "----------\n";
pass_through(str); // (3)
pass_through(std::move(str)); // (4)
return 0;
}
const std::string& version: Hello World
std::string&& version: Hello World
----------
const std::string& version: Hello World
const std::string& version: Hello World
When I use pass_through_f(), the result between (1) and (2) is different but they are same when pass_though() is called.
My question is, how is the result of (4) same with (3)? Here's type inferencing process that I thought:
In (4), return value of some function is r-value, so T of pass_through() is inferred as a type same with passed argument. Thus T is std::string&&.
std::string&& is r-value reference, and r-value reference is r-value so it calls overloaded_function() [II]. Thus its result is same with when std::forward is used.
But result is different with my thought. I think I'm misunderstanding something about the way std::forward works.
The problem is that in
template <typename T>
void pass_through(T&& param) {
overloaded_function(param);
}
param is a named variable. Since it has a name, it is an lvalue, even though it is a reference to an rvalue. Since it is an lvalue, you call the lvalue function.
You need std::forward to "re-cast" it back into a rvalue if it came in as an rvalue.
Also note that T is not string&&. Since you passed a string&&, T&& deduces T to string and the && is kept to make it an rvalue reference.