[cfe-dev] Questions about templates and different behavior of 4 different front-ends

Arthur O'Dwyer via cfe-dev cfe-dev at lists.llvm.org
Sun Apr 7 08:27:38 PDT 2019


On Sat, Apr 6, 2019 at 7:43 PM Alexander Us <coillol at yandex.ru> wrote:

> 06.04.2019, 19:36, "Arthur O'Dwyer" <arthur.j.odwyer at gmail.com>:
>
> On Fri, Apr 5, 2019 at 7:46 PM Alexander Us via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
>
>
> 1) First question is about variadic templates and how they are expanded.
> Consider the following code:
>
> template<typename A> struct S {};
>
> template<typename T, typename...Rest>
> using U = S<T, Rest...>; // is it valid?
>
> using SI = U<int>; // seems, that there should be S<int> and there is
> nothing ill-formed
>
> The problem here is that only gcc (starting from version 7.1) is able to
> compile this code. Other compilers report that using directive is
> ill-formed because it provides too many template parameters for S. Who is
> right in this case?
>
>
> My understanding is that all four compilers' behavior is correct. Template
> `U` is well-formed *only *when Rest... is an empty pack, and thus the
> program is ill-formed (no diagnostic required) via
> http://eel.is/c++draft/temp.res#8.3.  So technically any behavior at all
> is conforming to the standard. (This doesn't mean the compiler's behavior
> couldn't be made more user-friendly! An enhancement request might still be
> warranted.)
>
>
> 2) Second example is quite more complicated. The code is:
>
> template<typename A, typename B> struct S {};
>
> template<template<typename...> typename F>
> struct Flip {
>   template<typename A, typename B, typename...Rest>
>   using Type = F<B, A, Rest...>;
> };
>
> template<template<typename...> typename F, typename...Args>
> struct PartialApply {
>   template<typename...Rest>
>   using Type = F<Args..., Rest...>;
> };
>
> using X = typename PartialApply<Flip<S>::Type, int>::template Type<bool>;
> // there should be S<bool, int>
>
> Now this can be compiled only by icc. Other compilers become mad and start
> to report very strange error. For example, gcc says that there is "pack
> expansion of 'B'" in Flip. clang and msvc produce similar error. Is this
> code valid C++ after all?
>
>
> Well, same as above: the template `Flip<S>::Type` is invalid and thus
> causes the *entire program* to be ill-formed, no diagnostic required.
>
>
> Okay, it really seems that Flip is ill-formed. But changing Flip in the
> second example to:
>
> template<template<typename, typename> typename F>
> struct Flip {
>   template<typename A, typename B>
>   using Type = F<B, A>;
> };
>
> actually doesn't fix compilers. They're still saying that the program is
> incorrect because of this mystical pack expansion of 'B'. I assume that now
> it is a bug.
>

Sadly, no.  The culprit this time is that `Flip<S>::Type` is an alias
template, and the status quo as of C++17 is that it is specifically
forbidden to expand a parameter pack into the template argument list of an
alias template.
Here is a Godbolt illustrating the difference in what's supported for alias
templates versus class templates: https://godbolt.org/z/YAzHMc

This is CWG open issue 1430
<http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1430>.
Implementors seem to agree that mixing parameter packs and aliases is
troublesome for them. Clang's code-comments on CWG1430 talk about
"canonicalization," which I think means "asking whether two template
declarations declare the same template or different templates."
Example: https://godbolt.org/z/C9Qiwd  <https://godbolt.org/z/C9Qiwd>

Also if write similar code but in context of lambdas all compilers will
> work as expected. Does processing of variadic lambdas differ from
> processing of variadic templates?
>
>
> You'd have to show an example.
>
> auto fvar = [](auto f) {
>   return [f](auto a, auto...rest) { return f(a, rest...); };
> };
>
> auto sqr = [](auto a) { return a * a; };
>
> auto foo() {
>   return fvar(sqr)(2); // works fine
> }
>

Written out in C++03, we have
https://godbolt.org/z/eJ97M2
The problematic template is FRet<Sqr>::call. However, since that template
is itself generated *from *a template, the compiler would have to do extra
work to check it "prior to any instantiation" under
http://eel.is/c++draft/temp.res#8 — and so the compiler just doesn't do
that (optional) check, and so the code appears to work.
My understanding is that the template FRet<Sqr>::call (i.e.
`decltype(fvar(sqr))::operator()`) is just as ill-formed as your original
template `U` from example number one, and so this code does have undefined
behavior. However, I believe it's perfectly safe to use this code in
practice.
The major difference between this code and your original example number one
is that in this one the ill-formed template is created by template argument
substitution — it's not "visible" to the compiler prior to substituting
things into dependent expressions. Example: https://godbolt.org/z/LZvWhA
And the major difference between this code and your example number two is
that this one doesn't involve alias templates.

–Arthur
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20190407/4533766b/attachment-0001.html>


More information about the cfe-dev mailing list