Say I was trying to implement a concept meowable
that
- Integral types are meowable.
- Class types with member function
meow
are meowable. This is in the final target but the current question doesn't focus on it. - Tuple-like types with only meowable elements are meowable.
- std::ranges::range with meowable elements 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"?)
- How can I achieve my target?
CodePudding user response:
- 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>>{});
CodePudding user response:
Here is a simple recursive SFINAE solution for C 17 (and 11 for some adjustments):
https://godbolt.org/z/7sMjfq7ze
#include <type_traits>
template <typename T, typename = void>
struct has_meow : std::false_type {};
// this is a customization point to deduce tuple-like traits
// you can define some default logic here,
// but for the sake of example let it be false by default
template <typename>
struct is_tuple_like : std::false_type {};
template <typename T>
struct has_meow<T, std::void_t<decltype(std::declval<T>().mew())>> : std::true_type {};
template <typename T>
struct meowable : std::disjunction<std::is_integral<T>, has_meow<T>> {};
template <template <typename...> class TupleT, typename ... T>
struct meowable<TupleT<T...>> : std::conjunction<
is_tuple_like<TupleT<T...>>,
std::disjunction<meowable<T>...
> {};
#include <tuple>
#include <array>
// this will also catch std::pair
template <typename ... T>
struct is_tuple_like<std::tuple<T...>> : std::true_type {};
template <typename T, size_t N>
struct is_tuple_like<std::array<T, N>> : std::true_type {};
struct cat
{
void mew() {}
};
int main()
{
static_assert(meowable<int>::value);
static_assert(meowable<std::tuple<int, long>>::value);
static_assert(meowable<cat>::value);
static_assert(meowable<std::tuple<cat, long>>::value);
static_assert(meowable<std::tuple<int, std::tuple<cat, long>>>::value);
return 0;
};