Repository navigation
CWG3227 [temp.res.general] Unclear and incomplete requirement that packs must possibly be non-empty #878
Description
Activity
- changed the title
[-][temp.res.general] Unclear and incomplete requirement that packs must possibly be empty[/-][+][temp.res.general] Unclear and incomplete requirement that packs must possibly be non-empty[/+]on Mar 31, 2026 If a template does static_assert(false), then it has no valid specializations so vacuously it is IFNDR because of this wording.
Looks like the bullet 1-3 phrasing should be applied to bullet 6 as well.
Reacted by Brian BiAnother issue I realized is that:
template<typename...,typename...>void foo(){}
It is unclear if "every valid specialization" includes impossible to spell specializations. In this example there is nothing that would require either pack to be empty, however it is impossible to actually create a specialization that has the second pack containing anything.
This issue and the fifth point also seem applicable to the other bullets, for example:
#include<concepts> template<typename T>struct S{ template<typename U>void foo()requires std::same_as<T,int>{}//S<float>::foo has no valid specializations template<typename>S(){}//it is impossible to create a specialization of this constructor };
Perhaps it would make sense to split this into another issue.
Here is another example of when the fifth point would apply:
template<auto f>auto g=[](auto...x){ f(x...); }; void h(){} int main(){ g<h>(); }
g<h>is creating a closure type whose call operator is a template that can only be invoked with an empty template parameter pack.Also, something that should be considered is:
void foo(auto...x){ if constexpr(!sizeof...(x)){ int y(42,x...); } } int main(){ foo(); foo(1); }
This looks to be valid since x can be non-empty, but there is implementation divergence. GCC and MSVC accept this, while Clang and EDG reject it.
The restriction seemingly harms the expressivity of the language.
E.g., some components in the standard library require some type template arguments not to be explicitly specified, or (e.g., algorithms) render the program possibly ill-formed if some template arguments are explicitly specified. Withe valid-on-empty-only parameter packs, we can simply express the requirements as following.
template<class... EmptyBarrier, class T> requires (sizeof...(EmptyBarrier) == 0) void fun(T t);
This can disallow writing
fun<AnyArg>(x). (fun<>(x)is still allowed, but that should be very fine.)It makes programs which do static_assert(!sizeof...(pack)) IFNDR, which seems unintended.
What makes you believe this is unintended?
(I do agree with applying the "no valid specialization..." phrasing here.)
It makes programs which do static_assert(!sizeof...(pack)) IFNDR, which seems unintended.
What makes you believe this is unintended?
(I do agree with applying the "no valid specialization..." phrasing here.)
It would not make sense to allow
static_assert(false)but not allowstatic_assert(!sizeof...(pack)), and copying over the wording about static_assert from the previous bullets would allow both.template<class... EmptyBarrier, class T> requires (sizeof...(EmptyBarrier) == 0) void fun(T t);
This can disallow writing
fun<AnyArg>(x). (fun<>(x)is still allowed, but that should be very fine.)You can work around this by creating a helper like the classic always_false that was used with static_assert before:
#include<type_traits> template<typename...>struct RequiresEmpty:std::false_type{}; template<>struct RequiresEmpty<>:std::true_type{};
Or even just
always_false<pack...>||!sizeof...(pack).Reacted by A. JiangIt would not make sense to allow static_assert(false) but not allow static_assert(!sizeof...(pack)),
I don't follow.
static_assert(false)is expressly allowed (with a non-dependent argument) because people wanted to writeif constexpr (something) { static_assert(false); }Note that the question here is not "is this useful", but "is this a core issue".
It would not make sense to allow static_assert(false) but not allow static_assert(!sizeof...(pack)),
I don't follow.
static_assert(false)is expressly allowed (with a non-dependent argument) because people wanted to writeif constexpr (something) { static_assert(false); }Note that the question here is not "is this useful", but "is this a core issue".
static_assert(std::same_as<T,int>&&std::same_as<T,float>)is also valid, I do not see any requirement that the expression must be non-dependent.- changed the title
[-][temp.res.general] Unclear and incomplete requirement that packs must possibly be non-empty[/-][+]CWG3227 [temp.res.general] Unclear and incomplete requirement that packs must possibly be non-empty[/+]on Aug 30, 2026 Would this resolution make [dcl.struct.bind] example 1 IFNDR? I think the wording should focus on when the pack expansions happen for example in
union U:decltype(x)...rather than attempting to forbid always empty packs entirely.Well, for "regular" variadic templates, the existing (and the revised) wording prohibits always-empty packs wholesale, even if those are not per se "bad".
For structured bindings, I agree the example seems to convey the intent that an empty structured binding is unconditionally supported. I think we don't need wording beyond [temp.variadic] p7 to handle your example, though.
CWG3227 is updated.
Full name of submitter (unless configured in github; will be published with the issue): Jay Ghiron
Reference (section label): [temp.res.general]/6
Issue description:
There seem to be several issues with this text:
static_assert(!sizeof...(pack))IFNDR, which seems unintended.static_assert(false), then it has no valid specializations so vacuously it is IFNDR because of this wording.For the last point, it is unclear whether or not "every valid specialization" of a member function template in a class template refers to just the valid specializations for one member function template in a particular specialization of the class template, or instead collectively to all valid specializations of the member function templates for all specializations of the class template.