template<typename T> concept A = true;
template<typename... T> concept B = A<T...>;
Clang and gcc complains 'pack expansion used as argument for non-pack parameter of concept' at the 2nd line. MSVC compiles. Are the code well-formed or not?
Here is a more concrete example showing why the question matters. Say we want to define a concept with parameter pack called single_integral_or_all_floating_point
. It is true
if the pack has single integral type or all floating point types. It is false
otherwise.
template<typename... T>
concept single_integral_or_all_floating_point =
std::is_integral<T...>::value ||
(... && std::is_floating_point<T>::value);
If sizeof...(T)
is not 1
, there is a substitution failure on std::is_integral
and the 1st constraint is not satisfied.
Let's say we want to get rid of the old type traits and switch to concepts. Clang and gcc do not compile with a drop-in replacement like below because of 'pack expansion used as argument for non-pack parameter of concept'. MSVC compiles.
template<typename... T>
concept single_integral_or_all_floating_point =
std::integral<T...> || // error: pack expansion used as argument for non-pack parameter of concept
(... && std::floating_point<T>);
We have to come up a workaround for clang/gcc.
template<typename... T>
struct only {};
template<typename T>
struct only<T> {
using type = T;
};
template<typename... T>
concept single_integral_or_all_floating_point =
std::integral<typename only<T...>::type> ||
(... && std::floating_point<T>);
So if clang and gcc are correct, it is pretty awkward to use concepts in the similar cases.