C++ Logo


Advanced search

Re: Video Intro to WG21/D2288 C++ Designated Arguments

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Tue, 23 Nov 2021 14:57:22 -0500
On Tue, Nov 23, 2021 at 1:58 PM Hani Deek via Std-Proposals
<std-proposals_at_[hidden]> wrote:
> > template <typename T> struct Args { T x; int y = 0; };
> > template <typename T> void f(Args<T> args);
> > f({.x=5}); // nope
> Yes that code won't work, but what is the problem here? The problem is a limitation of template argument deduction. This is a problem whose scope is larger than the scope of our discussion.

This is only a problem that needs a solution if we are of the opinion
that structs are a good way to solve the named argument problem. If we
solve the named argument problem in some other way, we don't need to
make this code work. And remember: if you could solve this issue, it
wouldn't solve forwarding named parameters through variadic templates.

The overall point of bringing up these problems is to explain *why*
named parameters through structs is not a good solution to the
problem. Applying a bunch of patches to make it work isn't as good as
just providing named parameters in a sane and reasonable way.

> > Indeed, the only way to call such a function through forwarding is to
> > explicitly construct a parameter of the proper type, which requires
> > being able to *name* the type, which you can't do if you declared the
> > parameter in the function declaration.
> I don't think I understand this issue.
> Given the following template
> template<typename T0, typename T1>
> void f(struct { T0 &&a; T1 &&b; });
> You can forward the arguments as usual, e.g.
> std::forward<T0>(a), std::forward<T1>(b)

That's not the variadic forwarding problem. The variadic forwarding
problem is this:

class Type
  Type(float f, struct{int i, int j} params);

auto ptr1 = new T(23.5, {.i = 5, .j = 22}); //This works.
auto ptr2 = make_unique<T>(23.5, {.i = 5, .j = 22}); //This doesn't work.

Now, you can add a bunch of hacks to make this "work" to some degree.
But it wouldn't really work, and certainly not in all appropriate
forwarding mechanisms.

For example, imagine this variadic template:

template<typename T, typename ...Args>
T make_T_plus_1(Args... &&args)
  return T((std::forward<Args>(args) + 1)...);

Even if we allow a braced-init-list to be bundled into some kind of
construct that can later initialize some object, it wouldn't be able
to work like that.

However, if named parameters and arguments are a fundamental part of
the language, then `args` would know what names it was given. And when
you expand that pack into a function call, those named parameters can
be forwarded to that function call. The expression still evaluates,
but if a particular member of `args` has a named argument, the
expression in the expansion is assigned to that named argument.

So `make_T_plus_1<Type>(i = 4)` could work if `Type` has a constructor
that has a named argument `i` that accepts integers.

This applies to any forwarding scenario: `make_unqiue`,
`std::function`, the `in_place` constructors of `any`, `optional`, and
`variant`, and so forth.

Received on 2021-11-23 13:57:36