Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Do template structs need std::decay when using forwarding references in C++17?

In C++17, it will be possible to instantiate objects without specifying the template types. Basically, this code would compile:

std::pair p(2, 4.5);     // deduces to std::pair<int, double> p(2, 4.5);
std::tuple t(4, 3, 2.5); // same as auto t = std::make_tuple(4, 3, 2.5);

So, assuming this code below:

template<typename... Ts>
struct Foo
{
    Foo(Ts&&... ts) :
        ts{std::forward_as_tuple(ts...)}
    {}

    std::tuple<Ts...> ts;
};

int main()
{
    auto f = [] { return 42; };
    Foo foo{f, [] { return 84; }};
}

Should I use std::decay in the tuple declaration like this?

std::tuple<std::decay_t<Ts>...> ts;

Because this is how I'd write a function to return an object based on the deduced template type:

template<typename T>
auto make_baz(T&& t) -> baz<std::decay_t<T>>;

And I can see this pattern in the Foo's constructor, where it's using forwarding references to correctly pass the values to the tuple. I'm not sure if the type deduction here behaves the same way.

like image 283
Mário Feroldi Avatar asked Aug 25 '16 15:08

Mário Feroldi


People also ask

What is std :: decay used for?

In what situations is std::decay useful? It is used in the standard library e.g. when passing arguments to a thread. Those need to be stored, by value, so you cannot store e.g. arrays. Instead, a pointer is stored and so on.

Why is STD forward needed?

std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it. This allows rvalue arguments to be passed on as rvalues , and lvalues to be passed on as lvalues .


1 Answers

There's no need to change the internals of your class to make it work with class template argument deduction; that's what deduction guides are for.

The best place to start is by writing a make_X function; whether you provide one or not, deciding on the desired signature will let you know if you need to write an explicit deduction guide or can rely on the implicit deduction guides inferred from your constructors.

Indeed, a deduction guide, whether implicit or explicit, behaves the same as a make_X function (up to copy constructor elision).

Your desired makeFoo would have the following declaration:

template<typename... Ts>
auto makeFoo(Ts&&... ts) -> Foo<std::decay_t<Ts>...>;

Since this performs a transformation on the template arguments, you need to provide an explicit deduction guide; this is syntactically identical to the declaration of makeFoo, just with the auto make removed:

template<typename... Ts>
Foo(Ts&&... ts) -> Foo<std::decay_t<Ts>...>;

If you don't provide an explicit deduction guide, one will be generated from your constructor, without any type transformations other than those which occur during template argument deduction:

template<typename... Ts>
Foo(Ts&&... ts) -> Foo<Ts...>;

This isn't what you want, since it doesn't apply std::decay_t. Modifying the internals of your class (adding std::decay_t to ts) would work, but it's unnecessary when an explicit deduction guide solves the problem.

like image 114
ecatmur Avatar answered Sep 22 '22 06:09

ecatmur