Deduction Guides and Variadic Class Templates with Variadic Template Constructors - Mismatched Argument Pack Lengths

Deduction guides and variadic class templates with variadic template constructors - mismatched argument pack lengths

To simplify your example further, it appears that GCC does not implement variadic template arguments in deduction guides:

https://wandbox.org/permlink/4YsacnW9wYcoceDH

I didn't see any explicit mention of variadic templates in the wording for deduction guides in the standard or on cppreference.com. I see no interpretation of the standard that disallows this. Therefore I think this is a bug.

Deduction guide and variadic templates

This is gcc bug 80871. What follows is an explanation of why the code is well-formed (and clang is correct in deciding that t2 is a custom_tuple<int>).


The process for figuring out what to do with

custom_tuple t2(42);

basically involves synthesizing a bunch of functions and performing overload resolution on them. The relevant candidates are the synthesized functions from the one constructor and the deduction guide:

template <class... T, class... Args>
custom_tuple<T...> foo(Args... ); // the constructor

template <class... Args>
custom_tuple<Args...> foo(Args... ); // the deduction guide

From this point it's a choose your own adventure based on your interpretation of what a "trailing parameter pack" is according to [temp.arg.explicit]/3:

A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments. If all of the template arguments can be deduced, they may all be omitted; in this case, the empty template argument list <> itself may also be omitted.

T... isn't trailing

This case is easy. We only have one viable candidate (because T... isn't deducible) - the deduction-guide candidate. We deduce Args... as {int}, so we end up with custom_tuple<int>.

T... is trailing

Both gcc and clang actually do consider deduction to succeed for the constructor. So we go to the tiebreakers in [over.match.best]:

Given these definitions, a viable function F1 is defined to be a better function than another viable function F2 if [...]

  • F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in [temp.func.order], or, if not that,
  • F1 is generated from a deduction-guide ([over.match.class.deduct]) and F2 is not, or, if not that, [...]

For purposes of partial ordering, the relevant types are just those which correspond to function parameters, and we're allowed to ignore unused template parameters, so neither function template is considered more specialized than the other.

This leaves us to simply preferring the deduction-guide, which has been the simplest step of this whole process. We deduce Args... as {int}, so we end up with custom_tuple<int>.


Either way, custom_tuple<int> is the correct decision.

How to write a deduction guide for passing anonymous std::array of variable size?

This works for me on gcc 11 with -std=c++17:

#include <string>
#include <array>

using namespace std;

template <size_t N>
struct A
{
array<string, N> const value;
A (array<string, N>&& v): value {v} {}
};

template<typename T, size_t N>
A( T (&&) [N]) -> A<N>;

A a { { "hello", "there" } };

Live example

Function template overload resolution with two parameter packs

There are two issues here.


First, [temp.deduct.partial]/12 (I also quote the example since it is similar to yours) says:

In most cases, deduction fails if not all template parameters have values, but for partial ordering purposes a template parameter may remain without a value provided it is not used in the types being used for partial ordering. [ Note: A template parameter used in a non-deduced context is considered used. — end note ] [ Example:

template <class T> T f(int);            // #1
template <class T, class U> T f(U); // #2
void g() {
f<int>(1); // calls #1
}

— end example ]

The types being used for partial ordering are T... according to [temp.deduct.partial]/3:

The types used to determine the ordering depend on the context in which the partial ordering is done:

  • In the context of a function call, the types used are those function parameter types for which the function call has arguments.

  • ...

So the first unnamed template parameter pack class... does not affect the result of partial ordering. Since there are no other differences for the two function templates, neither is more specialized than the other, resulting an ambiguous call.

This may be related to bug 49505 of GCC.


Second, even though the second function template does not exist, the call should still be ill-formed. According to [temp.arg.explicit]/3:

... A trailing template parameter pack not otherwise deduced will be deduced to an empty sequence of template arguments ...

Only trailing template parameter pack can be deduced to an empty pack, while the first unnamed template parameter pack class... is not a trailing template parameter pack.

Both GCC (bug 69623) and Clang (bug 26435) have bugs for this issue.

Variadic Template Constructor to build Parent

14.8.2.1 Deducing template arguments from a function call

1 [...] For a function parameter pack that does not occur at the end of the parameter-declaration-list, the type of the parameter pack is a non-deduced context.

5 [...] [ Note: If a template-parameter is not used in any of the function parameters of a function template, or is used only in a non-deduced context, its corresponding
template-argument cannot be deduced from a function call and the template-argument must be explicitly specified. — end note ]

14.8.2.5 Deducing template arguments from a type

If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.

So, as you observe, Args... should be explicitly specified, but since this is not possible for a constructor, this is not going to work.

What can work is to pack all arguments meant for the base constructor in a tuple (live example), followed by the remaining arguments for the derived class:

template <typename T>
class C : public T
{
template<typename... A, size_t... I>
C(std::tuple<A...>&& a, sizes<I...>, double x) :
T(std::get<I>(std::move(a))...) { std::cout << "This is double\n"; }

public:
template<typename... A>
C(std::tuple<A...>&& a, double x) :
C(std::move(a), idx<sizeof...(A)>{}, x) { }
};

to be used as

int main() {
C<A> p(std::forward_as_tuple(1), 1.0);
C<A> r(std::forward_as_tuple(1u, 2.), 1.0);
C<B> q(std::forward_as_tuple('c', 0), 1.0);
}

where I have made your example a bit richer:

struct A {
A(int) { std::cout << "This is int\n"; }
A(unsigned, double) { std::cout << "This is unsigned, double\n"; }
};

struct B {
B(char, int) { std::cout << "This is char, int\n"; }
};

and the remaining boilerplate

template<size_t... I> struct sizes { using type = sizes<I...>; };

template<size_t N, size_t K = 0, size_t... I>
struct idx_t : idx_t<N, K+1, I..., K> {};

template<size_t N, size_t... I>
struct idx_t<N, N, I...> : sizes<I...> {};

template<size_t N>
using idx = typename idx_t<N>::type;

is there just until std::integer_sequence is available.

If you find std::forward_as_tuple too long a name, it's not hard to define you own e.g. pack:

template<typename... A>
constexpr std::tuple<A&&...>
pack(A&&... a) { return std::tuple<A&&...>{std::forward<A>(a)...}; }

Even so, syntax

C<B> q(pack('c', 0), 1.0);

does add a little overhead, but I find it natural that the end user has a hint of what is going on. Flattening to C<B> q('c', 0, 1.0); would not only be nearly impossible to implement if more parameters are added to the derived class constructor, but also ambiguous and confusing to the user. What would be really cool is a syntax like

C<B> q('c', 0; 1.0);

(also e.g. to separate input/output parameters in functions), but I haven't seen this in any language, only in textbooks.



Related Topics



Leave a reply



Submit