thlst thlst - 1 month ago 9x
C++ Question

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) :

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

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

Should I use
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.


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.