15

UPDATE1: C++17 added type deduction for constructors - which does not imply that the free function is an inferior solution.

UPDATE2: C++17 added guaranteed copy elision (the copy does not even take place conceptually). So with C++17 my code actually works and with optimal performance. But Martinho's code using brace initialisation for the return value is still the cleaner solution I believe. But checkout this answer from Barry and the comment from T.C.

OLD POST: Type deduction does not work for constructors (at least until and including C++11). The common solution is to rely on RVO (Return Value Optimisation) and write a make_XYZ template function that forwards its parameters to the constructor. An example is std::make_tuple.

Any template acrobat who knows a workaround to make this work when nocopy policy is in the way? A valid solution must still allow RVO to happen.

Additionally, will the requirement for any make_XYZ disappear with C++14?

#include <iostream>

template <typename T>
struct XYZ
{
    // remove following two lines to make the code compile
    XYZ (XYZ const & rhs) = delete; 
    XYZ (XYZ && rhs) = delete; 
    T i;
    XYZ (T i):i(i)
    {
    }
};

template <typename T>
XYZ<T> make_XYZ (T && i)
{
    return XYZ<T>(std::forward<T>(i));
}

int main ()
{
    auto x = make_XYZ(1);
    std::cout << x.i << std::endl;
}
Patrick Fromberg
  • 1,313
  • 11
  • 37
  • 2
    What if you change the return type of make_XYZ to const-reference? I'm not sure if you'll also need to change to `auto const&` at the call site. – John Zwinck Oct 17 '13 at 12:51
  • 1
    @JohnZwinck, no, that would crash – Patrick Fromberg Oct 17 '13 at 13:02
  • 1
    Are you sure? There are special rules for lifetime promotion of the referent of const references. You can read more about that here: http://stackoverflow.com/questions/2615162/return-value-not-a-reference-from-the-function-bound-to-a-const-reference-in/2615181#2615181 – John Zwinck Oct 17 '13 at 13:06
  • 2
    @JohnZwinck: Those don't allow you to magically keep function-local variables alive. I repeat: ref-to-`const` does not allow you to use refs to returned locals. – Lightness Races in Orbit Oct 17 '13 at 13:17
  • Yeah, you're right, that won't help here. Sigh, C++. :) – John Zwinck Oct 17 '13 at 13:19
  • 3
    If the ctor is not explicit, you may use a braced-init-list directly in the return statement. [Johannes Schaub](http://stackoverflow.com/users/34509/johannes-schaub-litb) has [suggested this approach here](http://stackoverflow.com/q/7935639/420683). – dyp Oct 17 '13 at 13:22
  • @JohnZwinck, I take back my comment about the crash, sorry. Even DyPs and Martinhos answers are the right ones, you are actually making a good point here. – Patrick Fromberg Oct 17 '13 at 14:31
  • 2
    @Patrick you can't return a reference to a local variable or temporary. It dies when the function ends. It might or might not crash, for such is the nature of undefined behaviour. – R. Martinho Fernandes Oct 17 '13 at 14:51
  • @R.MartinhoFernandes, the `auto const &` solution is clearly very suboptimal and a constref does not even work for me. However, why should [this answer](http://stackoverflow.com/a/2615199/2712726) not apply here? Seems I am still missing something (something that was clear to me when I knew less). – Patrick Fromberg Oct 17 '13 at 15:39
  • 2
    The lifetime extension only applies if you bind a temporary *directly* to the reference. It is not transitive, i.e., if you bind a new reference to that same reference, you don't get another lifetime extension. If you return by reference, binding another reference to that one won't make the function local live any longer. For `T f(); T const& r = f();` it works because `f()` creates a temporary and it is bound to `r` and lifetime extended. But for `T const& f(); T const& r = f();`, it doesn't because `f()` gives a reference not a temporary to extend. – R. Martinho Fernandes Oct 17 '13 at 16:13

2 Answers2

22

If there is a non-explicit constructor, it is indeed possible to return a non-copiable and non-movable type by value. See live example: http://coliru.stacked-crooked.com/a/89ef9d3115924558.

template <typename T>
XYZ<T> make_XYZ (T && i)
{
    return { std::forward<T>(i) };
}

The tricky bit here is that { ... } is not constructing a temporary and moving it to the return value. It is directly initialising the return value. There is no copy nor move, and that is irrelevant of whether any optimisation applies (it wouldn't compile if it required an optimisation to work).

However, since the type is not copyable nor movable, you will not be able to store it in a local variable by value. You can, however, use the old temporary lifetime extension trick to hold it:

auto&& x = make_XYZ(1);
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • +1 phantastic! I thought `XYZ<>` would at least have to allow for move constructor or move assignment, but this does it without either (nor copy constructor or copy assingment). – Walter Oct 17 '13 at 13:30
  • 2
    I know this is against the commenting conventions. But I can not hide it. I am impressed! – Patrick Fromberg Oct 17 '13 at 14:12
  • 3
    From the question: "A valid solution must still allow RVO to happen!" -- this answer is why arbitrary restrictions on the answers in the question should basically be ignored, and why SO asks for practical problems. :) – Yakk - Adam Nevraumont Oct 17 '13 at 14:48
  • 1
    This is really brilliant! Unfortunately, there's a case (probably rare) where it doesn't work: when `XYZ` has a constructor that takes a `std::initializer_list` (in which case this constructor steals the call to `XYZ(T)`). My workaround follows: create `struct tag {};' then add this constructor `XYZ(tag, T i) : XYZ(i) {}` (important: it's `(i)` and not `{i}`) and, in `make_XYZ`, do `return { tag{}, std::forward(i) };`. Maybe it's worth making `tag` and the delegating constructor `private` members of `XYZ` and `make_XYZ` a `friend`. What do you think? (Obrigado). – Cassio Neri Nov 14 '13 at 15:39
  • @CassioNeri yes, that sounds like a solid plan. – R. Martinho Fernandes Nov 14 '13 at 16:10
  • I just want to share this [related link regarding copy initialization](http://stackoverflow.com/a/20559713/2712726) – Patrick Fromberg Dec 13 '13 at 05:57
1

RVO is only ever an optimisation; the copy/move has to be available for use when returning an object (temporary or named) from a function.

I'd suggest using the make_XYZ only in a nonevaluated context, using decltype:

#include <utility>

struct noncopy {
    noncopy() {}
    noncopy(noncopy &&) = delete;
    noncopy(const noncopy &) = delete;
};

template<class T1, class T2>
struct noncopy_pair: public std::pair<T1, T2>, private noncopy {
    using std::pair<T1, T2>::pair;
};

template<class T1, class T2>
noncopy_pair<T1, T2> make_noncopy_pair(T1 &&t, T2 &&u);

int main() {
    auto &&x = decltype(make_noncopy_pair(1, 'c'))(1, 'c');
}

Unfortunately you have to repeat your arguments, but you can use a macro to work around this (and the macro is at least guaranteed safe, as no argument is evaluated more than once).

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    Unfortunately, you have been proven wrong by Martinho: copy/move does not have to be available!. bad luck. – Walter Oct 17 '13 at 13:30
  • @Walter copy/move has to be available *for RVO*; Martinho's solution doesn't use RVO. – ecatmur Oct 17 '13 at 13:33
  • Yes, but your answer can be read to mean that copy/move has to be available for a solution to the OP's problem. And that's how I read it (and still do). – Walter Oct 17 '13 at 14:33
  • @Walter I've tried to clarify my answer; the key to the new syntax is that it doesn't involve returning an actual object from the function. – ecatmur Oct 17 '13 at 17:00