Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Which substitution failures are not allowed in requires clauses?

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.

like image 809
Rumburak Avatar asked Jan 01 '20 19:01

Rumburak


1 Answers

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.

like image 63
Barry Avatar answered Nov 05 '22 22:11

Barry