Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting appropriate storage value type?

Tags:

c++

c++11

Suppose we have a class Box as follows:

struct Base {}

template<typename T>
struct Box : Base
{
    template<typename... Args>
    Box(Args&&... args)
        : t(forward<Args>(args)...)
    {}

    T t;
}

And then we have a function MakeBox:

template<typename X>
Base* MakeBox(X&& x)
{
    return new Box<???>(forward<X>(x));
}

The type X is deduced from the parameter used in the call to MakeBox.

We then need to calculate somehow from X the appropriate "storage type" parameter T.

I think if we just naively use:

    return new Box<X>(forward<X>(x));

then this will cause problems.

Clearly std::bind and std::function need to deal with these problems, how do they do it?

Is std::decay helpful in anyway here?

like image 462
Andrew Tomazos Avatar asked Oct 06 '22 12:10

Andrew Tomazos


2 Answers

If I understand correctly what you want to achieve, then you need to use std::decay. Supposing you are supplying an object of type S to MakeBox(), the universal reference X&& will be resolved in such a way to make the function argument either of type S& or S&& depending on whether your argument is (respectively) an lvalue or an rvalue.

To achieve this and due to C++11 rules for universal references, in the first case the template argument will be deduced as X=S& (here X would not be OK as an argument to Box<>, because your member variable has to be an object and not an object reference), while in the second case it will be deduced as X=S (here X would be fine as an argument to Box<>). By applying std::decay you will also implicitly apply std::remove_reference to the deduced type X before supplying it as a template argument to Box<>, you are going to make sure that X will always be equal S and never S& (please keep in mind, that X is never going to be deduced as S&& here, it will be either S or S&).

#include <utility>
#include <type_traits>
#include <iostream>

using namespace std;

struct Base {};

template<typename T>
struct Box : Base
{
    template<typename... Args>
    Box(Args&&... args)
        : t(forward<Args>(args)...)
    {
    }

    T t;
};

template<typename X>
Base* MakeBox(X&& x)
{
    return new Box<typename decay<X>::type>(forward<X>(x));
}

struct S
{
    S() { cout << "Default constructor" << endl; }
    S(S const& s) { cout << "Copy constructor" << endl; }
    S(S&& s) { cout << "Move constructor" << endl; }
    ~S() { cout << "Destructor" << endl; }
};

S foo()
{
    S s;
    return s;
}

int main()
{
    S s;

    // Invoking with lvalue, will deduce X=S&, argument will be of type S&
    MakeBox(s);

    // Invoking with rvalue, will deduce X=S, argument will be of type S&&
    MakeBox(foo());

    return 0;
}

If you are interested, here is a very good lesson by Scott Meyers where he explains how universal references behave:

Scott Meyers on universal references

P.S.: This answer has been edited: my original answer suggested to use std::remove_reference<>, but std::decay turned out to be a better choice. Credits to the question poster @Andrew Tomazos FathomlingCorps, who pointed that out, and to @Mankarse, who first proposed it in a comment to the original question.

like image 153
Andy Prowl Avatar answered Oct 10 '22 09:10

Andy Prowl


For the provided example (1) does what you want and stores the value. For other cases (such as when x is an array) you can use std::decay to decay it to a pointer and store that.

like image 40
yuri kilochek Avatar answered Oct 10 '22 10:10

yuri kilochek