Wrapping Constructors

I tried this, but it generates a runtime error and I'm not sure why:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <functional>
#include <string>
 
struct TestA
{
    std::string s;
    TestA(){}
    TestA(std::string const&s) : s(s) {}
    TestA(TestA const&) = default;
    TestA(TestA &&) = default;
    TestA &operator=(TestA const&) = default;
    TestA &operator=(TestA &&) = default;
    ~TestA() = default;
};
 
template<typename T, typename... Args>
std::function<T &&(Args...)> WrapCtor()
{
    return [](Args... args) -> T &&
    {
        return T(args...);
    };
}
 
int main()
{
    auto f = WrapCtor<TestA, std::string const&>();
    TestA inst = f("test");
    std::cout << inst.s << std::endl;
}
http://ideone.com/36ahLs

Hopefully it is clear what I am trying to do here...
1
2
3
4
5
6
7
8
9
template<typename T, typename... Args>
std::function<T &&(Args...)> WrapCtor()
{
    return [](Args... args) -> T &&
    {
        return T(args...);
    };
    // *** warning *** returning (r-value) reference to temporary
}
Gah, rvalue references + move semantics + temporaries still confuse me.

Fixed: http://ideone.com/VW42gb
Complete noob question: I really would like to understand what is happening here. Is there a tutorial anyone can link me to that will help, please?
I'm not sure about tutorials, I kind of just learned by looking at the new language features and how they were intended to be used.

Which part confuses you first?
We can just rely on copy-elision:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <functional>

struct A
{
    std::string s;
    A() = default ;
    A( std::string const& s ) : s(s) {}
};

template< typename T, typename... ARGS > T make( ARGS... args )
{ return T( args... ) ; } // copy-elision

int main()
{
    A inst = make<A>( std::string("test") ); // copy-elision
    std::cout << inst.s << '\n' ;

    std::function< A( const std::string&& ) > f = &make< A, const std::string&& > ;
    A another_inst = f( std::string("another test") ) ; // copy-elision
    std::cout << another_inst.s << '\n' ;
}
@JLBorges why do you use an rvalue reference on line 20?
Which part confuses you first?

Lines 17-24

EDIT: And thank you for helping me.
Last edited on
17
18
19
20
21
22
23
24
template<typename T, typename... Args>
std::function<T (Args...)> WrapCtor()
{
    return [](Args... args) -> T
    {
        return T(args...);
    };
}


The template statement declares that it takes a type T, which I use that the type to instantiate, and then an arbitrary number of types called Args, which I use for identifying and calling the intended constructor.

The function returns a std::function that wraps a function returning T and taking as arguments the types specified by Args.

WrapCtor returns a lambda which takes the argument types specified by Args and names this list of arguments args. It returns T, of course.

Finally the return inside the lambda returns an instance of T given the parameters given by args.
Last edited on
> Is there a tutorial anyone can link me to that will help, please?

This is a good one:
http://thbecker.net/articles/rvalue_references/section_01.html

> @JLBorges why do you use an rvalue reference on line 20?

It is not really required, I was just being fussy.
We can rely on the compiler to eliminate the unnecessary copy of a std::string.

This too (a slight modification to the code in the original post) would be fine:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
#include <functional>
#include <string>

struct TestA
{
    std::string s;
    TestA(){}
    TestA(std::string const&s) : s(s) {}
    TestA(TestA const&) = default;
    TestA(TestA &&) = default;
    TestA &operator=(TestA const&) = default;
    TestA &operator=(TestA &&) = default;
    ~TestA() = default;
};

template<typename T, typename... Args>
std::function<T /* && */ (Args...)> WrapCtor()
{
    return [](Args... args) -> T /*&&*/ // return the temporary by value
    {
        return T(args...);
    };
}

int main()
{
    auto f = WrapCtor<TestA, std::string const&>();
    TestA inst = f("test");
    std::cout << inst.s << std::endl;
}
Last edited on
Yes, that is what my "Fixed: <link>" code looks like ;)
Thank you L B :)
> Yes, that is what my "Fixed: <link>" code looks like ;)

Ok, didn't see that. The page was taking too long to load and I got impatient.


@Script Coder
You may have missed this link, I added it later via an edit:
http://thbecker.net/articles/rvalue_references/section_01.html
I think Script Coder was confused about the ellipses, not the rvalue references, or at least that was the impression I got.
Ah! He could try this link then:
http://www.devx.com/cplus/Article/41533
I was confused with the ellipse, std::function and the lambda syntax. (need less to say, I have not worked with C++11 much).

One thing still confusing me is why in the original code there were two &'s instead of one. (I understand why they should not be there though).
> One thing still confusing me is why in the original code there were two &'s instead of one.

The first link I posted explains the two &'s (as in: int&& rvr = std::rand() ;)
Thank you guys :)
@JLBorges that article on rvalue references is confusing:
Article wrote:
Finally, if you implement

void foo(X&&);

but neither one of

void foo(X&);

and

void foo(X const &);

then, according to the final version of C++11, foo can be called on r-values, but trying to call it on an l-value will trigger a compile error.
So why is std::move an exception?
http://en.cppreference.com/w/cpp/utility/move
> So why is std::move an exception?

It is not an exception; std::move() is a function template, and standard template argument deduction rules for function templates applies to it as well.

This is clarified on page 8 of the article:
Secondly, there is a special template argument deduction rule for function templates that take an argument by rvalue reference to a template argument:

template<typename T>
void foo(T&&);

Here, the following apply:

When foo is called on an lvalue of type A, then T resolves to A& and hence, by the reference collapsing rules above, the argument type effectively becomes A&.

When foo is called on an rvalue of type A, then T resolves to A, and hence the argument type becomes A&&.


1
2
3
4
5
6
7
8
9
10
template < typename T > void foo( T&& ) {}

void bar( int&& ) {}

int main()
{
    int i = 9 ;
    foo(i) ; // fine; foo is a template; T&& collapses to int&
    bar(i) ; // *** error: can't bind lvalue to rvalue reference
}
Last edited on
Topic archived. No new replies allowed.