The snippet of code compiled with std=c++17
as the only compiler flag ...
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){});
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){};
}
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:
Now, it seems that clang treats std::function< void(Int<Ts>...) >
as a non-dependent type, with the following reasoning:
Int<Ts>
is a non-dependent type (correct).Int<Ts>
(i.e. Int<Ts>...
) is also a non-dependent "type" (?).void(Int<Ts>...)
are non-dependent, it is a non-dependent type (obviously incorrect).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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With