Trying to specialize based on validity of array size:
// base template
template<int p, typename T = void>
struct absolute {
operator int () const { return 0; }
};
// positive case template
template<int p>
struct absolute<p, typename std::void_t<int[p]>> {
operator int () const { return p; }
};
// negative case template
template<int p>
struct absolute<p, typename std::void_t<int[-p]>> {
operator int () const { return -p; }
};
int main() {
std::cout << absolute<5>() << std::endl;
std::cout << absolute<-5>() << std::endl;
std::cout << absolute<0>() << std::endl;
}
Above code works nicely with gcc but fails to compile with clang.
Clang generates the error: redefinition of template struct 'absolute'
Who is right?
Both with gcc and with clang (if we remove the negative specialization to bring clang back to the game), not clear why absolute<0>()
selects the base template. There is nothing wrong with int[0]
as well as with std::void_t<int[0]>
which seems to be more specialized:
// base template
template<int p, typename T = void>
struct absolute {
operator int () const { return -1; }
};
// positive case template
template<int p>
struct absolute<p, typename std::void_t<int[p]>> {
operator int () const { return p; }
};
int main() {
std::cout << absolute<5>() << std::endl; // 5
std::cout << absolute<0>() << std::endl; // -1, why not 0?
}
And... if the base template is just declared without implementation, as:
// base template
template<int p, typename T = void>
struct absolute;
Both gcc and clang would fail to compile, complaining on invalid use of incomplete type for the call: absolute<0>()
. Even though it seems to fit the specialized case.
Why is that?
Regarding Clang's redefinition error, see this question.
Originally template-ids of alias templates such as std::void_t
would simply be replaced with their aliasing type without checking the arguments for substitution failure. This was changed with CWG issue 1558. This only changed the standard to require substitution failure in the template arguments to be done, but doesn't clarify whether two templates that would be equivalent after replacing the alias should be considered equivalent. Clang considers them to be equivalent, but GCC doesn't. This is the open CWG issue 1980.
With -pedantic-errors
GCC reports a hard error already for
std::cout << absolute<5>() << std::endl;
in the specialization
template<int p>
struct absolute<p, typename std::void_t<int[-p]>>
because supposedly the array size is not a constant expression. The size of an array must be a converted constant expression of type std::size_t
. A converted constant expression may only use non-narrowing conversions. So it is true that -p
with p = 5
converted to std::size_t
is not a constant expression, making the type int[-p]
ill-formed, but I think that should cause a substitution failure, not a hard error. [temp.deduct/8] of the C++17 standard (draft N4659) says:
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments.
And this applies here. The non-normative examples given in following the quote even include negative array sizes as example for substitution failure.
It is particularly strange that for absolute<-5>()
GCC does not report the equivalent error on the
template<int p>
struct absolute<p, typename std::void_t<int[p]>>
specialization, where int[p]
would evaluate to int[-5]
which also doesn't have a converted constant expression size.
absolute<0>()
chooses the primary template, because array sizes are required to be larger than zero, making the partial specializations both non-viable. Zero-sized arrays are a language extension that can be disabled with -pedantic-errors
in GCC and Clang.
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