While trying to create a toy DSL, I managed to come across an odd construct that neither clang nor gcc handled the way I expected:
#include <utility>
template <class...>
struct disjunction {};
template <auto>
struct literal {};
template <class T, T Min, T Max>
using range = decltype([]<T... Cs>(std::integer_sequence<T, Cs...>) {
return disjunction<literal<T{Min + Cs}>..., literal<Max>>{};
}(std::make_integer_sequence<T, Max - Min>{}));
template <class T, T... Cs> requires (sizeof...(Cs) == 1)
constexpr literal<Cs...> operator""_c() { return {}; }
template <class T, T Min, T Max>
constexpr range<T, Min, Max> operator-(literal<Min>, literal<Max>) { return {}; }
constexpr auto digits = "0"_c - "9"_c;
using expected = const disjunction<
literal<'0'>, literal<'1'>, literal<'2'>, literal<'3'>, literal<'4'>,
literal<'5'>, literal<'6'>, literal<'7'>, literal<'8'>, literal<'9'>>;
static_assert(std::is_same_v<decltype(digits), expected>);
Here, clang crashes and gcc evaluates digits as the value 0 of type int.
I managed to find the following workaround using partial template specialization:
namespace detail {
template <class, auto, auto> struct range;
template <class T, T... Cs, T Min, T Max>
struct range<std::integer_sequence<T, Cs...>, Min, Max> {
using type = disjunction<literal<T{Min + Cs}>..., literal<Max>>;
};
} // namespace detail
template <class T, T Min, T Max>
using range = typename detail::range<
std::make_integer_sequence<T, Max - Min>, Min, Max>::type;
In this case, the implementation of range allows the static_assert above to succeed for both clang and gcc, but it would be nice to be able to implement this template without a class template helper.
Finally, while using the initial implementation of range, if the expression "0"_c - "9"_c is wrapped in decltype(...) like this:
using digits = decltype("0"_c - "9"_c);
using expected = disjunction<
literal<'0'>, literal<'1'>, literal<'2'>, literal<'3'>, literal<'4'>,
literal<'5'>, literal<'6'>, literal<'7'>, literal<'8'>, literal<'9'>>;
static_assert(std::is_same_v<digits, expected>);
Then clang does not crash or report a diagnostic for the static_assert as shown here. However, gcc still fails, and when checking the actual type of digits like this:
template <class Type>
auto f() { static_assert(sizeof(Type) == 0); }
int main() { f<digits>(); }
clang crashes again with
error: cannot compile this l-value expression yet
int main() { f<digits>(); }
^~~~~~~~~
This last error message seems to suggest clang acknowledging this construct as well-formed, but I'm not certain.
As the title reads, I'd like to seek an understanding of whether my attempted implementation of range using decltype of an immediately-invoked lambda expression is well-formed.