13

The snippet of code compiled with std=c++17 as the only compiler flag ...

  • ... compiles successfully with GCC 9.1. Godbolt
  • ... issues a compiler error with Clang 8.0.0 (error below snippet). Godbolt

Question: is this a bug in the Clang compiler or is GCC wrong to accept this code or is something else at play?

#include <functional>
#include <tuple>

template <typename... Ts>
struct Foo
{
    template <typename T>
    using Int = int;

    // Function that accepts as many 'int' as there are template parameters
    using Function = std::function< void(Int<Ts>...) >;

    // Tuple of as many 'int' as there are template parameters
    using Tuple = std::tuple< Int<Ts>... >;

    auto bar(Function f)
    {
        std::apply(f, Tuple{}); // Error with Clang 8.0.0
    }
};

int main()
{
    auto foo = Foo<char, bool, double>{};
    foo.bar([](int, int, int){});
}

What strikes me as odd is that Clang's error indicates it successfully aliased Tuple as std::tuple<int, int, int> but it wrongly aliases Function as just std::function<void(int)>, with only one instead of three arguments.

In file included from <source>:2:
In file included from /opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/functional:54:
/opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/tuple:1678:14: error: no matching function for call to '__invoke'
      return std::__invoke(std::forward<_Fn>(__f),
             ^~~~~~~~~~~~~
/opt/compiler-explorer/gcc-8.3.0/lib/gcc/x86_64-linux-gnu/8.3.0/../../../../include/c++/8.3.0/tuple:1687:19: note: in instantiation of function template specialization 'std::__apply_impl<std::function<void (int)> &, std::tuple<int, int, int>, 0, 1, 2>' requested here
      return std::__apply_impl(std::forward<_Fn>(__f),
                  ^
<source>:19:14: note: in instantiation of function template specialization 'std::apply<std::function<void (int)> &, std::tuple<int, int, int> >' requested here
        std::apply(f, Tuple{}); // Error
             ^
<source>:26:9: note: in instantiation of member function 'Foo<char, bool, double>::bar' requested here
    foo.bar([](int, int, int){});

Additional Research

As other users in the comments already pointed out, making the Int template alias dependent on the type T fixes the issue:

template <typename T>
using Int = std::conditional_t<true, int, T>;

Something else I found out, just referring to the Function type from the outside also makes it work as expected/desired:

int main()
{
    auto f = Foo<char, bool, double>::Function{};
    f = [](int, int, int){};
}
Maarten Bamelis
  • 2,243
  • 19
  • 32
  • [`Function` is actually right in the sense of its declaration](https://coliru.stacked-crooked.com/a/eeff5b8f94df02e8) but something blows up somewhere else – Lightness Races in Orbit Jul 17 '19 at 16:50
  • 3
    Bug of clang for me. Code seems not ill formed. – Jarod42 Jul 17 '19 at 16:51
  • Also why isn't `std::apply` working for me -.- – Lightness Races in Orbit Jul 17 '19 at 16:54
  • @LightnessRacesinOrbit: clang version on coliru is 5.0.0-3~16.04.1 (tags/RELEASE_500/final) – Jarod42 Jul 17 '19 at 16:58
  • I think Clang doesn't like the fact that `T` isn't being used the type alias. A short way around this is `template using Int = decltype(sizeof(T), 0);` – David G Jul 17 '19 at 17:02
  • As the bug with `void_t`. – Jarod42 Jul 17 '19 at 17:07
  • This also seems to work: `template using Int = std::conditional_t;`. Coliru's clang++ is still choking on `std::apply` though – alter_igel Jul 17 '19 at 17:34
  • It seems that Clang treats `std::function< void(Int...) >` as an instantiation-dependent but not (type-)dependent type, so the type of everything related to `Function` is determined at definition time instead of at instantiation time. https://godbolt.org/z/rMLsyA – cpplearner Jul 17 '19 at 18:43
  • @cpplearner It still strikes me as very odd that everything works fine for the `Tuple` type alias then; somehow, Clang is perfectly fine with that one. Additionally, and I have added this to the question, I also found out that just referring to `Foo<…>::Function` from outside the class works just fine, weird huh? – Maarten Bamelis Jul 17 '19 at 19:45
  • 1
    @LightnessRacesinOrbit Yes, I also found this out by referring to `Foo<…>::Function` from outside the class and then it behaves as a function taking three integers... and even the `Tuple` alias works just fine. A bug it is then! – Maarten Bamelis Jul 17 '19 at 19:54
  • Anyone knows what this status ‘CD4’ mean? 1558 CD4 Unused arguments in alias template specializations Unknown https://clang.llvm.org/cxx_dr_status.html – Hui Jul 17 '19 at 22:00
  • @MaartenBamelis Yep deffo. Nasty! – Lightness Races in Orbit Jul 17 '19 at 23:59
  • @Hui [_"CD4: A DR/DRWP or Accepted/WP issue not resolved in C++14 but included in the Committee Draft advanced for balloting at the June, 2014 WG21 meeting."_](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html) Not sure it's relevant though – Lightness Races in Orbit Jul 18 '19 at 00:01
  • 3
    Update: Bug was logged for this: https://bugs.llvm.org/show_bug.cgi?id=42654 (as Maarten asked me in person) – JVApen Jul 18 '19 at 06:56

1 Answers1

3

TL;DR: it's a clang bug, but there's also a bug in the standard.

First be aware that in C++, templates are handled in two steps:

  1. Constructs that are not dependent on the template parameters are built when the enclosing template is defined.
  2. Constructs that are dependent are built when the enclosing template is instantiated.

Now, it seems that clang treats std::function< void(Int<Ts>...) > as a non-dependent type, with the following reasoning:

  1. Int<Ts> is a non-dependent type (correct).
  2. Thus, pack expansion containing Int<Ts> (i.e. Int<Ts>...) is also a non-dependent "type" (?).
  3. Because all components of void(Int<Ts>...) are non-dependent, it is a non-dependent type (obviously incorrect).
  4. Because the name std::function is non-dependent and the template argument void(Int<Ts>...) is a non-dependent non-pack-expansion type, std::function< void(Int<Ts>...) > is a non-dependent type.

(Note that the "non-pack-expansion" check makes it different from the Tuple case.)

Therefore, when Foo is defined, the type name Function is treated as naming a non-dependent type, and is immediately built, and pack expansion (which happens during instantiation) is not accounted. Consequently, all uses of Function is replaced with the "desugared" type std::function< void(int) >.

Furthermore, clang has the notion of instantiation-dependent, which means the construct is not dependent, but it still somehow involves the template parameters (e.g. the construct is only valid for some parameters). std::function< void(Int<Ts>...) > is treated as a instantiation-dependent type, so when the template is instantiated, clang still performs substitution for using Function = std::function< void(Int<Ts>...) >. As a result, Function gets the correct type, but this does not propagate to uses of Function in the definition of Foo.


Now heres the bug in the standard.

Whether a type is dependent is defined in [temp.dep.type]:

A type is dependent if it is

  • a template parameter,
  • a member of an unknown specialization,
  • a nested class or enumeration that is a dependent member of the current instantiation,
  • a cv-qualified type where the cv-unqualified type is dependent,
  • a compound type constructed from any dependent type,
  • an array type whose element type is dependent or whose bound (if any) is value-dependent,
  • a function type whose exception specification is value-dependent,
  • denoted by a simple-template-id in which either the template name is a template parameter or any of the template arguments is a dependent type or an expression that is type-dependent or value-dependent or is a pack expansion [ Note: This includes an injected-class-name of a class template used without a template-argument-list. — end note ] , or
  • denoted by decltype(expression), where expression is type-dependent.

Note that it doesn't say that a function type whose parameter list contains pack expansions is a dependent type, only that "a compound type constructed from any dependent type" and "a function type whose exception specification is value-dependent" are dependent. Neither is helpful here.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • A difficult matter but you explained the two bugs very well! I see no error in your reasoning, so I will accept this as the correct answer. – Maarten Bamelis Jul 20 '19 at 16:44