https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108169
mail at jhellings dot nl changed:
What |Removed |Added
----------------------------------------------------------------------------
CC| |mail at jhellings dot nl
--- Comment #3 from mail at jhellings dot nl ---
I looked a bit further into this and into what the standard says. GCC seems to
derive the wrong type for non-type template parameters in a few cases both (1)
when they have a placeholder type such as auto and (2) when they use a normal
type. See https://jhellings.nl/article?articleid=1 for the full analysis.
My findings (summary):
Let V be a non-type template parameter. The C++20 standard defines decltype(V)
as follows (Clause 1 of Section [dcl.type.decltype]):
"For an expression E, the type denoted by decltype(E) is defined as follows:
... otherwise, if E is an unparenthesized id-expression naming a non-type
template-parameter, decltype(E) is the type of the template-parameter after
performing any necessary type deduction;"
In the above, the type of a template-parameter is defined as follows (Clause 6
of Section [temp.param]):
"A non-type template-parameter shall have one of the following (possibly
cv-qualified) types: ... The top-level cv-qualifiers on the template-parameter
are ignored when determining its type."
Hence, decltype(V) with V a non-type template parameter should never yield a
type with a top-level const qualifier.
In GCC 12 (x86-64 gcc 12.2 on Compiler Explorer), GCC does not follow this rule
in the following case: if the type of V is a structural class type T, then GCC
always yields a ``const T''.
See, for example, the following program _that does not use placeholder types_:
/*
* @author{Jelle Hellings}.
*/
#include <iostream>
#include <typeinfo>
#include <type_traits>
/*
* Format the type @{Type} and print it to standard output. The qualifiers
* @{qs...} are used to include further details on @{Type}.
*/
template<class Type>
void type_printer(const auto&... qs)
{
if constexpr (std::is_lvalue_reference_v<Type>) {
return type_printer<std::remove_reference_t<Type>>(qs..., "&");
}
if constexpr (std::is_rvalue_reference_v<Type>) {
return type_printer<std::remove_reference_t<Type>>(qs..., "&&");
}
if constexpr (std::is_const_v<Type>) {
return type_printer<std::remove_const_t<Type>>(qs..., "const");
}
if constexpr (std::is_volatile_v<Type>) {
return type_printer<std::remove_volatile_t<Type>>(qs..., "volatile");
}
if constexpr (std::is_array_v<Type>) {
return type_printer<std::remove_extent_t<Type>>(qs..., "[]");
}
if constexpr (std::is_pointer_v<Type>) {
return type_printer<std::remove_pointer_t<Type>>(qs..., "*");
}
((std::cout << qs << " "), ...);
std::cout << '`' << typeid(Type).name() << '`' << std::endl;
}
/*
* Print the details on the type @{Type}, the type of a non-type template
* parameter with value @{Value}, and on the type of expressions involving
* @{Value}. The printed data will be labeled with @{case_name}.
*/
template<class Type, Type Value = Type{}>
void inspect_template_argument(const auto& case_name)
{
type_printer<Type>(case_name);
type_printer<decltype(Value)>(case_name);
}
/*
* A dummy struct type.
*/
struct dummy {};
/*
* Entry-point of the program.
*/
int main()
{
/* We look at most combinations of types and values. */
inspect_template_argument<int>("{int}");
inspect_template_argument<const int>("{c-int}");
inspect_template_argument<dummy>("{dummy}");
inspect_template_argument<const dummy>("{c-dummy}");
}
The above program SHOULD return:
{int} `int`
{int} `int`
{c-int} const `int`
{c-int} `int`
{dummy} `5dummy`
{dummy} `5dummy`
{c-dummy} const `5dummy`
{c-dummy} `5dummy`
But, in x86-64 gcc 12.2 (on Compiler Explorer), it returns
{int} `i`
{int} `i`
{c-int} const `i`
{c-int} `i`
{dummy} `5dummy`
{dummy} const `5dummy` << WRONG to the best of my understanding
{c-dummy} const `5dummy`
{c-dummy} const `5dummy` << WRONG to the best of my understanding
Note that in x86-64 gcc 11.3 (on Compiler Explorer), the output was more-wrong,
as also the const-qualifier for non-class types was preserved in some cases.
Indeed, x86-64 gcc 11.3 (on Compiler Explorer) returns:
{int} `i`
{int} `i`
{c-int} const `i`
{c-int} const `i` << WRONG & FIXED to the best of my understanding
{dummy} `5dummy`
{dummy} const `5dummy` << WRONG to the best of my understanding
{c-dummy} const `5dummy`
{c-dummy} const `5dummy` << WRONG to the best of my understanding
The issues detected above carry over to the usage of placeholder types, leading
to the bug reported in this bug report.