I want to implement a has_no_duplicates<...>
type trait that evaluates to std::true_type
if the passed variadic type list has no duplicate types.
static_assert(has_no_duplicates<int, float>{}, "");
static_assert(!has_no_duplicates<float, float>{}, "");
Let's assume, for the scope of this question, that I want to do that using multiple inheritance.
When a class inherits from the same type more than once, an error occurs.
template<class T>
struct type { };
template<class... Ts>
struct dup_helper : type<Ts>... { };
// No errors, compiles properly.
dup_helper<int, float> ok{};
// Compile-time error:
// base class 'type<float>' specified more than once as a direct base class
dup_helper<float, float> error{};
I assumed I could've used void_t
to "detect" this error, but I couldn't implement a working solution following the code samples from cppreference .
This is what I tried:
template<class, class = void>
struct is_valid
: std::false_type { };
// First try:
template<class T>
struct is_valid<T, std::void_t<decltype(T{})>>
: std::true_type { };
// Second try:
template<class T>
struct is_valid<T, std::void_t<T>>
: std::true_type { };
For my third try, I tried delaying the expansion of dup_helper<...>
using a wrapper class that took dup_helper
as a template template parameter, like wrapper<dup_helper, ...>
and expanded it inside of void_t
.
Unfortunately, all my tries resulted in the aforementioned error always preventing compilation.
I assume this type of error is not detectable as a "substitution failure", but I'd like confirmation.
Is this kind of error actually impossible to detect using void_t
? (Will it always result in a compilation failure?)
Is there a way to detect it without causing compilation to fail? (Or a non- void_t
workaround that still makes use of the "multiple inheritance trick")?
As @Canoninos noted, the problem is that:
it isn't the declaration of
dup_helper<T, T>
which causes an error but its definition [...].
Or, in Standardese, the error occurs outside the "immediate context" ( [temp.deduct] ) of the substitution:
8 - [...] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure. [ Note: The evaluation of the substituted types and expressions can result in side effects such as the instantiation of class template specializations and/or function template specializations, the generation of implicitly-defined functions, etc. Such side effects are not in the “immediate context” and can result in the program being ill-formed. — end note ]
Here the error occurs while instantiating dup_helper<float, float>
so is not in the "immediate context".
One multiple inheritance trick that's very close to yours involves adding an extra layer of inheritance, by indexing the multiple bases:
helper<<0, 1>, <float, float>>
+
+----+----+
v v
ix<0, float> ix<1, float>
+ +
v v
t<float> t<float>
This gives us a helper class with a valid definition and that can be instantiated but not cast to its ultimate base classes, because of ambiguity:
static_cast<t<float>>(helper<...>{}); // Error, SFINAE-usable
Example .
That's my solution using meta-programming and type-list idiom. I use this code as part of my library implementing reflection for C++. I think there is no need in void_t or inheritance at all to solve this task.
template <typename ...Args>
struct type_list
{};
using empty_list = type_list<>;
// identity
template<typename T>
struct identity
{
using type = T;
};
// is_typelist
template<typename T>
struct is_typelist: std::false_type
{};
template<typename ...Args>
struct is_typelist<type_list<Args...>>: std::true_type
{};
template<typename T>
struct check_typelist
{
using type = void;
static constexpr void *value = nullptr;
static_assert(is_typelist<T>::value, "T is not a type_list!");
};
// indexof
namespace internal {
template<typename T, typename V, std::int64_t index>
struct typelist_indexof_helper: check_typelist<T>
{};
template<typename H, typename ...T, typename V, std::int64_t index>
struct typelist_indexof_helper<type_list<H, T...>, V, index>:
std::conditional<std::is_same<H, V>::value,
std::integral_constant<std::int64_t, index>,
typelist_indexof_helper<type_list<T...>, V, index + 1>
>::type
{};
template<typename V, std::int64_t index>
struct typelist_indexof_helper<empty_list, V, index>: std::integral_constant<std::int64_t, -1>
{};
}
template<typename T, typename V>
using typelist_indexof = ::internal::typelist_indexof_helper<T, V, 0>;
template<typename T, typename V>
struct typelist_exists: std::integral_constant<bool, typelist_indexof<T, V>::value >= 0>
{};
// remove_duplicates
namespace internal {
template<typename P, typename T>
struct typelist_remove_duplicates_helper: check_typelist<T>
{};
template<typename ...P, typename H, typename ...T>
struct typelist_remove_duplicates_helper<type_list<P...>, type_list<H, T...>>:
std::conditional<typelist_exists<type_list<T...>, H>::value,
typelist_remove_duplicates_helper<type_list<P...>, type_list<T...>>,
typelist_remove_duplicates_helper<type_list<P..., H>, type_list<T...>>
>::type
{};
template<typename ...P>
struct typelist_remove_duplicates_helper<type_list<P...>, empty_list>: identity<type_list<P...>>
{};
}
template<typename T>
using typelist_remove_duplicates = ::internal::typelist_remove_duplicates_helper<empty_list, T>;
template<typename ...Args>
struct has_no_duplicates: std::integral_constant<bool, std::is_same<type_list<Args...>,
typename typelist_remove_duplicates<type_list<Args...>>::type>::value>
{};
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.