I am trying to write a function that returns true, if all template parameters are unique.
Pseudo code:
template<typename... Ts>
auto types_are_unique() -> bool {
return (no two Ts are the same);
}
Instead of "manually" comparing every T
with every other, I wanted to use the fact that multiple inheritance is not allowed if two or more base classes are identical.
#include <utility>
template <typename T>
struct X {};
template <typename... Ts>
struct Test : X<Ts>... {};
template <typename... Ts>
constexpr auto types_are_unique() -> bool {
return false;
}
template <typename... Ts>
requires requires { Test<Ts...>{}; }
constexpr auto types_are_unique() -> bool {
return true;
}
int main() {
static_assert(types_are_unique<int, float>()); // compiles
static_assert(not types_are_unique<int, int>()); // fails
}
Both gcc and clang agree that this fails to compile because unique.cpp:7:8: error: duplicate base type ‘X<int>’ invalid
.
This comes as surprise, reading https://en.cppreference.com/w/cpp/language/constraints#Requires_expressions
The substitution of template arguments into a requires-expression used in a declaration of a templated entity may result in the formation of invalid types or expressions in its requirements, or the violation of semantic constraints of those requirements. In such cases, the requires-expression evaluates to false and does not cause the program to be ill-formed.
Are clang and gcc wrong? Or is cppreference wrong? Or (most likely) am I reading this wrong?
Which classes of substitution failures are not allowed to occur in requires
expressions? Please refer to the respective section of the current draft for C++20.
Which classes of substitution failures are not allowed to occur in requires expressions?
The same kinds as anywhere else. This isn't actually specific to concepts. Here's a slightly modified version of your example that demonstrates the same issue with C++11:
using size_t = decltype(sizeof(0));
template <typename T>
struct X {};
template <typename... Ts>
struct Test : X<Ts>... {};
template <typename...> struct typelist { };
template <typename... T, size_t = sizeof(Test<T...>)>
constexpr auto types_are_unique(typelist<T...>) -> bool {
return true;
}
constexpr auto types_are_unique(...) -> bool {
return false;
}
// ok
static_assert(types_are_unique(typelist<int, float>()));
// compile error
static_assert(not types_are_unique(typelist<int, int>()));
The problem has to do with what is and is not in the immediate context of substitution. This is a term that is introduced in [temp.deduct]/8 but isn't really thoroughly defined†:
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. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. — end note ] Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure. [ Note: The substitution into types and expressions can result in effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]
The general idea is that only failures in a declaration lead to a substitution failure, while failures in a definition lead to hard errors that cause the program to be ill-formed. In the example here, the problem isn't the declaration of Test<int, int>
it's in the definition of it (at the point where we get to its base classes). And that is considered too late - a failure outside of the immediate context is no longer a substitution failure.
†We even have a core issue (1844) asking for a better definition.
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