Say you wanted to define a concept that checks that all alternatives of a variant are convertible to int
. You could do it like so:
#include <concepts>
#include <type_traits>
#include <utility>
#include <variant>
template <typename T>
concept ConvertibleToInt = std::convertible_to<T, int>;
template <typename T>
concept AllAlternativesConvertibleToInt = []<auto... i>(std::index_sequence<i...>){
return (ConvertibleToInt<std::variant_alternative_t<i, T>> && ...);
}(std::make_index_sequence<std::variant_size_v<T>>{});
If the constraint fails, you get a relatively descriptive error (for C++ standards):
static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
<source>:12:15: error: static assertion failed
12 | static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:12:15: note: because 'std::variant<char, int, float, double, std::nullptr_t>' (aka 'variant<char, int, float, double, std::nullptr_t>') does not satisfy 'AllAlternativesConvertibleToInt'
<source>:8:43: note: because '[]<auto ...i>(std::index_sequence<i...>) {
return (ConvertibleToInt<std::variant_alternative_t<i, variant<char, int, float, double, std::nullptr_t> > > && ...);
}(std::make_index_sequence<std::variant_size_v<variant<char, int, float, double, std::nullptr_t> > >{})' evaluated to false
8 | concept AllAlternativesConvertibleToInt = []<auto... i>(std::index_sequence<i...>){
| ^
1 error generated.
However, the error message doesn't show which of the alternatives failed the constraint. For that to happen, AFAIK you'd need to use only concepts.
I was unable to find a way to have a parameter pack from 0
to std::variant_size_v
, other than writing it manually like so:
template <typename T>
concept ConvertibleToInt = std::convertible_to<T, int>;
template <typename T, auto... i>
concept AllAlternativesConvertibleToIntImpl = (ConvertibleToInt<std::variant_alternative_t<i, T>> && ...);
template <typename T>
concept AllAlternativesConvertibleToInt = AllAlternativesConvertibleToIntImpl<T, 0, 1, 2, 3, 4>;
static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
Note that now the error message says alternative #4 does not satisfy the constraint:
<source>:24:15: error: static assertion failed
24 | static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:24:15: note: because 'std::variant<char, int, float, double, std::nullptr_t>' (aka 'variant<char, int, float, double, std::nullptr_t>') does not satisfy 'AllAlternativesConvertibleToInt'
<source>:22:43: note: because 'AllAlternativesConvertibleToIntImpl<std::variant<char, int, float, double, std::nullptr_t>, 0, 1, 2, 3, 4>' evaluated to false
22 | concept AllAlternativesConvertibleToInt = AllAlternativesConvertibleToIntImpl<T, 0, 1, 2, 3, 4>;
| ^
<source>:19:48: note: because 'std::variant_alternative_t<4, variant<char, int, float, double, std::nullptr_t>>' (aka 'std::nullptr_t') does not satisfy 'ConvertibleToInt'
19 | concept AllAlternativesConvertibleToIntImpl = (ConvertibleToInt<std::variant_alternative_t<i, T>> && ...);
| ^
<source>:16:28: note: because 'std::convertible_to<std::nullptr_t, int>' evaluated to false
16 | concept ConvertibleToInt = std::convertible_to<T, int>;
| ^
/opt/compiler-explorer/gcc-14.2.0/lib/gcc/x86_64-linux-gnu/14.2.0/../../../../include/c++/14.2.0/concepts:79:30: note: because 'is_convertible_v<std::nullptr_t, int>' evaluated to false
79 | concept convertible_to = is_convertible_v<_From, _To>
| ^
1 error generated.
Is it possible to do this without writing the parameter pack manually?
Weijun Zhou's comment made me realize you can use a function to check which of the alternatives fails the constraint (if any).
Based on that, I came up with this:
#include <algorithm>
#include <variant>
template <typename T>
concept ConvertibleToInt = std::convertible_to<T, int>;
template <typename T>
consteval auto get_failing_index()
{
return []<auto... i>(std::index_sequence<i...>){
return std::min({ -1uz, (!ConvertibleToInt<std::variant_alternative_t<i, T>> ? i : -1uz)... });
}(std::make_index_sequence<std::variant_size_v<T>>{});
}
template <auto i, typename T, typename Default>
struct GuardedVariantAlternativeHelper
{
using Type = std::variant_alternative_t<i, T>;
};
template <typename T, typename Default>
struct GuardedVariantAlternativeHelper<-1uz, T, Default>
{
using Type = Default;
};
template <auto i, typename T, typename Default>
using GuardedVariantAlternative = GuardedVariantAlternativeHelper<i, T, Default>::Type;
template <typename T, std::size_t i>
concept AlternativeConvertibleToInt = ConvertibleToInt<GuardedVariantAlternative<i, T, int>>;
template <typename T>
concept AllAlternativesConvertibleToInt = AlternativeConvertibleToInt<T, get_failing_index<T>()>;
static_assert(AllAlternativesConvertibleToInt<std::variant<>>);
static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double>>);
static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
Which results in:
<source>:38:15: error: static assertion failed
38 | static_assert(AllAlternativesConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>>);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<source>:38:15: note: because 'std::variant<char, int, float, double, std::nullptr_t>' (aka 'variant<char, int, float, double, std::nullptr_t>') does not satisfy 'AllAlternativesConvertibleToInt'
<source>:34:43: note: because 'AlternativeConvertibleToInt<std::variant<char, int, float, double, std::nullptr_t>, get_failing_index<std::variant<char, int, float, double, std::nullptr_t> >()>' evaluated to false
34 | concept AllAlternativesConvertibleToInt = AlternativeConvertibleToInt<T, get_failing_index<T>()>;
| ^
<source>:31:39: note: because 'GuardedVariantAlternative<4UL, variant<char, int, float, double, std::nullptr_t>, int>' (aka 'std::nullptr_t') does not satisfy 'ConvertibleToInt'
31 | concept AlternativeConvertibleToInt = ConvertibleToInt<GuardedVariantAlternative<i, T, int>>;
| ^
<source>:5:28: note: because 'std::convertible_to<std::nullptr_t, int>' evaluated to false
5 | concept ConvertibleToInt = std::convertible_to<T, int>;
| ^
/opt/compiler-explorer/gcc-14.2.0/lib/gcc/x86_64-linux-gnu/14.2.0/../../../../include/c++/14.2.0/concepts:79:30: note: because 'is_convertible_v<std::nullptr_t, int>' evaluated to false
79 | concept convertible_to = is_convertible_v<_From, _To>
| ^
1 error generated.
And is pretty much what I wanted.