9

Consider the following code:

#include <tuple>
#include <iostream>

template <class T>
struct custom_wrapper
{
    template <class Arg>
    custom_wrapper(Arg arg): data(arg) {}
    T data;
};

template <class Arg>
custom_wrapper(Arg arg) -> custom_wrapper<Arg>;

template <class... T>
struct custom_tuple
{
    template <class... Args>
    custom_tuple(Args... args): data(args...) {}
    std::tuple<T...> data;
};

template <class... Args>
custom_tuple(Args... args) -> custom_tuple<Args...>;

int main(int argc, char* argv[])
{
    custom_wrapper<int> w1(42);  // OK
    custom_wrapper w2(42);       // OK
    custom_tuple<int> t1(42);    // OK
    custom_tuple t2(42);         // Fails
    return 0;
}

The line that fails return the following error under g++7:

variadic_deduction_guide.cpp: In instantiation of 'custom_tuple<T>::custom_tuple(Args ...) [with Args = {int}; T = {}]':
variadic_deduction_guide.cpp:31:23:   required from here
variadic_deduction_guide.cpp:19:45: error: no matching function for call to 'std::tuple<>::tuple(int&)'
     custom_tuple(Args... args): data(args...) {}

Is that normal or is that a compiler bug?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Vincent
  • 57,703
  • 61
  • 205
  • 388

1 Answers1

4

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Hmm, GCC and Clang trunk disagree on OP's code, and I'm not really convinced here. First, is `T` "trailing"? (That's a rather ill-defined term.) Second, can you demonstrate how the constructor is more specialized than the deduction guide? – T.C. Jun 03 '17 at 01:53
  • @T.C. But cool to see Clang working with deduction-guides! That must be pretty recent. – Barry Jun 03 '17 at 01:56
  • The context here is a function call, so ["the types used \[for partial ordering\] are those function parameter types for which the function call has arguments"](https://timsong-cpp.github.io/cppwp/temp.deduct.partial#3.1). And 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"](https://timsong-cpp.github.io/cppwp/temp.deduct.partial#12), and`T...` isn't. So exactly how is one more specialized than the other? In which direction does the deduction fail, and how? – T.C. Jun 03 '17 at 02:02
  • 1
    @T.C. You know what's funny is that I found another answer of mine on almost the same question where I (a) gave the opposite answer and (b) [filed this gcc bug](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80871). It seems that I cannot keep this stuff straight. – Barry Jun 03 '17 at 02:07
  • @T.C. Rewrote everything. – Barry Jun 03 '17 at 02:16