Say I was trying to implement a concept meowable
that
meow
are meowable. This is in the final target but the current question doesn't focus on it.Then I came up with this implementation(simplified as I could):
#include <concepts>
#include <type_traits>
#include <ranges>
#include <utility>
#include <tuple>
template<class T>
concept meowable_builtin = std::integral<T>;
template<class T, std::size_t I>
concept has_tuple_element = requires (T t) {
typename std::tuple_element<I, T>::type;
{ get<I>(t) } -> std::convertible_to<std::tuple_element_t<I, T>&>;
};
template<class T>
concept tuple_like = requires {
typename std::tuple_size<T>::type;
{ std::tuple_size_v<T> } -> std::convertible_to<std::size_t>;
} &&
[]<std::size_t...I>(std::index_sequence<I...>) {
return (has_tuple_element<T, I> && ...);
} (std::make_index_sequence<std::tuple_size_v<T>>{});
template<class T> struct is_meowable: std::false_type{};
template<meowable_builtin T> struct is_meowable<T>: std::true_type{};
template<tuple_like T>
struct is_meowable<T>
: std::bool_constant<
[]<std::size_t...I>(std::index_sequence<I...>) {
return (is_meowable<std::tuple_element_t<I, T>>::value && ...);
} (std::make_index_sequence<std::tuple_size_v<T>>{})
> {};
template<class T>
concept meowable_tuple = tuple_like<T> && is_meowable<T>::value;
template<class T>
concept meowable = is_meowable<T>::value;
static_assert(meowable<int>);
//static_assert(tuple_like<std::tuple<int>>);
static_assert(is_meowable<std::tuple<int>>::value);
But some compilers don't like it (https://godbolt.org/z/5vMTEhTdq):
1. GCC-12 and above: internal compiler error.
2. GCC-11: accepted.
3. Clang-13 and above: static_assert fired.
4. MSVC-v19: accepted.
However, if I uncomment the second last line of code, all compilers are happy. (Instantiation point of concepts?)
So my questions are:
- Why this behavior? (compiler bug or something like "ill-formed NDR"?)
This is apparently a bug of GCC-trunk and Clang-trunk, the issue here is that GCC/Clang doesn't properly handle the template partial specialization based on the concept initialized by the lambda. Reduced
template<class>
concept C = [] { return true; } ();
template<class T>
struct S {};
template<class T>
requires C<T>
struct S<T> { constexpr static bool value = true; };
// static_assert(C<int>);
static_assert(S<int>::value);
- How can I achieve my target?
Replace lambda with the template function based on the reduced result
template<class T, std::size_t...I>
constexpr bool all_has_tuple_element(std::index_sequence<I...>) {
return (has_tuple_element<T, I> && ...);
}
template<class T>
concept tuple_like = requires {
typename std::tuple_size<T>::type;
{ std::tuple_size_v<T> } -> std::convertible_to<std::size_t>;
} && all_has_tuple_element<T>(std::make_index_sequence<std::tuple_size_v<T>>{});