-->

Deduction guides and variadic class templates with

2019-07-15 08:22发布

问题:

Consider the following class definition and deduction guide:

template <typename... Ts>
struct foo : Ts...
{
    template <typename... Us>
    foo(Us&&... us) : Ts{us}... { }
};

template <typename... Us>
foo(Us&&... us) -> foo<Us...>;

If I try to instantiate foo with explicit template arguments, the code compiles correctly:

foo<bar> a{bar{}}; // ok

If I try to instantiate foo through the deduction guide...

foo b{bar{}};
  • g++7 produces a compiler error:

    prog.cc: In instantiation of 'foo<Ts>::foo(Us ...) [with Us = {bar}; Ts = {}]':
    prog.cc:15:16:   required from here
    prog.cc:5:27: error: mismatched argument pack lengths while expanding 'Ts'
         foo(Us... us) : Ts{us}... { }
                               ^~~
    
  • clang++5 explodes:

    #0 0x0000000001944af4 PrintStackTraceSignalHandler(void*) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944af4)
    #1 0x0000000001944dc6 SignalHandler(int) (/opt/wandbox/clang-head/bin/clang-5.0+0x1944dc6)
    #2 0x00007fafb639a390 __restore_rt (/lib/x86_64-linux-gnu/libpthread.so.0+0x11390)
    #3 0x0000000003015b30 clang::Decl::setDeclContext(clang::DeclContext*) (/opt/wandbox/clang-head/bin/clang-5.0+0x3015b30)
    ...
    clang-5.0: error: unable to execute command: Segmentation fault
    

live example on wandbox

While clang++ is definitely bugged (reported as issue #32673), is g++ correct in rejecting my code? Is my code ill-formed?

回答1:

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.



回答2:

Since foo has a constructor the compiler generates an implicit deduction guide based on the constructor:

// implicitly generated from foo<T...>::foo<U...>(U...)
template<class... T, class... U> foo(U...) -> foo<T...>;

template<class... T> foo(T...) -> foo<T...>; // explicit

The problem then is that gcc is preferring the implicit guide, and thus deducing T to {} and U to {bar}; clang (since 5.0.0 according to godbolt) prefers the explicit guide. This is an overload resolution problem; when two deduction guides are found to be ambiguous, explicit deduction guides are preferred over implicit deduction guides. But clang and gcc disagree over whether the deduction guides are ambiguous:

template<class... T, class... U> int f(U...) { return 1; }
template<class... T> int f(T...) { return 2; }
int i = f(1, 2);

This program (not involving deduction guides at all) is accepted by gcc (selecting #1) and rejected by clang (as ambiguous). Retracing our steps, this means that going back to deduction guides clang gets to tie-break the ambiguity by selecting the explicit deduction guide over the implicit deduction guide (generated from the constructor template), while gcc cannot do this as it has already selected the implicit deduction guide as a preferred candidate.

We can construct an even simpler example:

template<class... T, int = 0> int f(T...);  // #1
template<class... T> int f(T...);  // #2
int i = f(1, 2);

Again, gcc (incorrectly) selects #1 while clang rejects as ambiguous.

Importantly, we can workaround this issue by adding another explicit deduction guide that gcc will nevertheless prefer to the implicit deduction guide generated from the constructor:

template <typename U, typename... Us>
foo(U&& u, Us&&... us) -> foo<U, Us...>;

This is preferred (when more than 0 arguments are supplied) since it is binding the first argument to a singular parameter as opposed to a pack. In the 0-argument case it does not matter which deduction guide (between the original explicit guide and the implicitly generated guide) is selected since both come up with the same result, foo<>. It is safe to add this for all compilers, since it is preferred in the 1+-argument case and is not a candidate in the 0-argument case.

Example.