I have some experience with std::enable_if. IIRC, it is about if a well-formed expression results in true return back the user type T (if given) or void via nested type alias.
template<bool,typename = void>
struct enable_if;
template<typename T>
struct enable_if<true,T>{
using type = T;
};
template <typename T, typename = void>
struct base_template{
enum { value= false};
};
template <typename T>
struct base_template<T, typename enable_if<std::is_integral<T>::value>::type> {
enum { value= true};// something useful...
};
struct some{};
static_assert(base_template<some>::value,"F*"); //error: static assertion failed: F
static_assert(base_template<int>::value,"F*");
But in boost.Hana I see this trait when<> and its implementation is like
template <bool condition>
struct when;
template <typename T, typename = when<true>>
struct base_template{
enum { value= false};
};
template <typename T>
struct base_template<T, when<std::is_integral<T>::value>> {
enum { value= true};// something useful...
};
struct some{};
static_assert(base_template<int>::value,"F*");
static_assert(base_template<some>::value,"F*");<source>:28:15: error: static assertion failed: F*
How the SFINAE works here? though the std::is_integral<some>::value is going to result in false, it doesn't mean(it does?) that the when<false> is ill-formed and should dispatch the instantiation to the primary class template. Am I missing anything here?
It's the same general idea. You can use enable_if_t or decltype in basically the same way. Now, you're probably used to seeing SFINAE partial specializations like this:
template<class T, class U = void>
struct Foo {};
template<class T>
struct Foo<T, decltype(T::bar())> {};
... Foo<X> ...
Here, Foo<X> is first expanded by the compiler to Foo<X, void> (because you didn't provide U at the "call site", so the default U = void is filled in instead). Then, the compiler looks for the best-matching specialization of class template Foo. If decltype(X::bar()) is in fact void, then Foo<T, decltype(T::bar())> [with T=X] will be a perfect match for Foo<X, void>. Otherwise, the generic Foo<T, U> [with T=X, U=void] will be used instead.
Now for the Hana when example.
template<bool> struct when {};
template<class T, class U = when<true>>
struct Foo {};
template<class T>
struct Foo<T, when<T::baz>> {};
... Foo<X> ...
Here, Foo<X> is first expanded by the compiler to Foo<X, when<true>> (because you didn't provide U at the "call site", so the default U = when<true> is filled in instead). Then, the compiler looks for the best-matching specialization of class template Foo. If when<X::baz> is in fact when<true>, then Foo<T, when<T::baz>> [with T=X] will be a perfect match for Foo<X, when<true>>. Otherwise, the generic Foo<T, U> [with T=X, U=when<true>] will be used instead.
You can replace the simple expression T::baz in my example with any arbitrarily complicated boolean expression, as long as it's constexpr-evaluable. In your original example, the expression was std::is_integral<T>::value.
My CppCon 2017 session "A Soupçon of SFINAE" walks through some similar examples.
Related
I'm working with some classes that may have a nested type defined. Whenever this nested type is not defined I pretend like this nested type is void. For example, here is the code that should work:
struct HasNestedTypeDefined {
using Nested = int;
};
struct VoidNestedTypeDefined {
using Nested = void;
};
struct NoNestedTypeDefined {
};
static_assert(!std::is_same_v<reveal_nested_type< HasNestedTypeDefined>::type, void>);
static_assert( std::is_same_v<reveal_nested_type< HasNestedTypeDefined>::type, int>);
static_assert( std::is_same_v<reveal_nested_type<VoidNestedTypeDefined>::type, void>);
static_assert(!std::is_same_v<reveal_nested_type<VoidNestedTypeDefined>::type, int>);
static_assert( std::is_same_v<reveal_nested_type< NoNestedTypeDefined>::type, void>);
static_assert(!std::is_same_v<reveal_nested_type< NoNestedTypeDefined>::type, int>);
So far I've got this working:
template<typename T, typename R = void>
struct enable_if_type {
using type = R;
};
template<typename T, typename = void>
struct reveal_nested_type {
using type = void;
};
template<typename T>
struct reveal_nested_type<T, typename enable_if_type<typename T::Nested>::type> {
using type = typename T::Nested;
};
I thought that SFINAE should work even without using enable_if_type, like that:
template<typename T, typename = void>
struct reveal_nested_type {
using type = void;
};
template<typename T>
struct reveal_nested_type<T, typename T::Nested> {
using type = typename T::Nested;
};
but the partial specialization is never used in this case, even if the typename T::Nested is defined. Why does SFINAE ignore it? The follow up question: is there a more idiomatic way to implement the same?
Note your last attempt
template<typename T, typename = void>
struct reveal_nested_type {
using type = void;
};
template<typename T>
struct reveal_nested_type<T, typename T::Nested> {
using type = typename T::Nested;
};
actually does work for reveal_nested_type<VoidNestedTypeDefined>. What's happening is that because of the default template argument on the primary reveal_nested_type class template, reveal_nested_type<T> means the same as reveal_nested_type<T, void>. So the partial specialization reveal_nested_type<T, typename T::Nested> will only match if the nested type exists and the nested type is identical to void.
Here std::void_t is a nice way to fix this:
template<typename T, typename = void>
struct reveal_nested_type {
using type = void;
};
template<typename T>
struct reveal_nested_type<T, std::void_t<typename T::Nested>> {
using type = typename T::Nested;
};
This is a typical use of void_t, just using SFINAE to require that a dependent type is valid, and/or that a dependent expression is valid (via the dependent type decltype(expr)), but ignoring what that type actually is.
The above is essentialy what I'd probably use for C++03 to C++17. But if you can use C++20 constraints, another way would be:
template <typename T>
struct reveal_nested_type {
using type = void;
};
template <typename T> requires requires { typename T::Nested; }
struct reveal_nested_type<T> {
using type = typename T::Nested;
};
(Yes, two requires keywords. The first introduces a constraint on the template which expects a Boolean expression, and the second introduces a requires-expression which gives a Boolean expression from a list of requirements.)
We have this little metaprogramming marvel called std::conditional described here. In the same reference it says that a possible implementation is
template<bool B, class T, class F>
struct conditional { typedef T type; };
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };
So if in code I do something like
typename std::conditional<true,int,double>::type a;
the compiler will follow the first definition and if I do something like
typename std::conditional<false,int,double>::type b
the compiler will take the second. Why does that work ? What compilation rule is in place here ?
In short it has to do with template specialization rules. These are like function overloads but for types.
Here compiler prefers a more specialized type to the more general type.
template<bool B, class T, class F>
struct conditional { typedef T type; };
template<class T, class F>
struct conditional<false, T, F> { typedef F type; };
So if a template instantiation coditional<false, ...> is seen by compiler it finds:
the general template template<bool B, class T, class F> struct conditional
all its specializations: template<class T, class F> struct conditional<false, T, F>
At that point it tries to match as many specialized arguments as possible and ends up selecting the specialization with false.
For example introducing another version like:
template<class F>
struct conditional<false, int, F> { typedef int type; };
and instantiating a template type like conditional<false, int, double> will prefer specialization
template<class F> struct conditional<false, int, F>
to
template<class T, class F> struct conditional<false, T, F> which is more general compared to the version with 2 specialized paramaters.
Some tricks at this point:
It is even OK to just declare the most generic case (i.e. generic form of the template is just declared but not defined) and only have specializations for cases you really intend to have. All non-specialized cases will result in compile errors and asking the user to specialize the case for a particular type.
Why does that work ? What compilation rule is in place here ?
I'm not an expert but I'll try to explain from the pratical point of view.
Hoping to use the rights terms...
With
template <bool B, class T, class F>
struct conditional { typedef T type; };
(but, with C++11, I prefer
template <bool B, typename T, typename>
struct conditional { using type = T; };
) you declare the template
template <bool, typename, typename>
struct conditional;
and define the generic (not specialized) version.
With
template< class T, class F>
struct conditional<false, T, F> { typedef F type; };
(or, in C++11,
template <typename T, typename F>
struct conditional<false, T, F> { using type = F; };
) you define a partial specialization of conditional
When the template arguments of a class (or struct) match two or more definitions, the compliler choose the more specialized one; so, for
typename std::conditional<false,int,double>::type
both definitions of the class match so the compiler choose the specialized one (the specialized with false) anche type is double.
For
typename std::conditional<true,int,double>::type a;
only the generic version match, so type is int.
In most of the template meta programming main rule at play here is SFINAE(Substitution failure is not an error). According to this rule if compiler not find the type then instead of throwing an error it moves forward and check if there is any future statement that can provide type information.
I want to be able to specialize a type based on whether a container has a specified typedef for example
class SomethingElse {};
class Something {
using Type = int;
};
static constexpr bool value = ChooseIfType<Something>::value;
Is there a way for ChooseIfType to return false when the type does not have the typedef Type?
I feel like there is an easy way to do this but I cannot figure it out.
Thanks!
Just use std::void_t (or a C++11 alternative):
template<typename T, typename = std::void_t<>>
struct ChooseIfType : std::false_type {};
template<typename T>
struct ChooseIfType<T, std::void_t<typename T::Type>> : std::true_type {};
live demo
The solution makes use of SFINAE. The default is never malformed and creates a trait with value false. The compiler tries to match all template specializations (only one in this case). If T has a member type Type, then ChooseIfType<T, void_t<typename T::Type>> is more specialized than ChooseIfType<T, void_t<>>. If it doesn't, then it's not a viable specialization and the default is selected, but Substitution Failure Is Not An Error.
as per cppreference, a C++11 void_t implementation could look like this:
template<typename... Ts> struct make_void { typedef void type;};
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
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>
{};
DEMO
I'm trying to write a metafunction that checks whether all types passed as a variadic template parameter are distinct. It seems that the most performant way to do this is to inherit from a set of classes and detect, whether there is an error.
The problem is that compilation fails in the following code, while I would expect SFINAE to work.
Edit. The question is not "how to write that metafunction" but "how do I catch that double inheritance error and output false_type when it happens". AFAIK, it's possible only with SFINAE.
template <typename T>
struct dummy {};
// error: duplicate base type ‘dummy<int>’ invalid
template <typename T, typename U>
struct fail : dummy<T>, dummy<U> {};
template <typename T>
true_type test(fail<T, T> a = fail<T, T>());
false_type test(...);
int main() {
cout << decltype(test<int>())::value << endl;
}
Live version here.
Edit. Previously I've tried to do this with specialization failure, but it didn't work either with the same compilation error.
template <typename T>
struct dummy {};
template <typename T, typename U>
struct fail : dummy<T>, dummy<U>, true_type {};
template <typename T, typename U = void>
struct test : false_type {};
template <typename T>
struct test<T, typename enable_if<fail<T, T>::value, void>::type> : true_type {};
Live version here.
You can't catch duplicate inheritance with SFINAE, because it is not one of the listed reasons for deduction to fail under 14.8.2p8 [temp.deduct]; equally, it is because the error occurs outside the "immediate context" of template deduction, as it is an error with the instantiation of your struct fail.
There is however a very similar technique which is suitable for use in your case, which is to detect an ambiguous conversion from a derived class to multiple base classes. Clearly the ambiguous base classes can't be inherited directly from a single derived class, but it works fine to inherit them in a linear chain:
C<> A<int>
| /
C<int> A<char>
| /
C<char, int> A<int>
| /
C<int, char, int>
Now a conversion from C<int, char, int> to A<int> will be ambiguous, and as ambiguous conversion is listed under 14.8.2p8 we can use SFINAE to detect it:
#include <type_traits>
template<class> struct A {};
template<class... Ts> struct C;
template<> struct C<> {};
template<class T, class... Ts> struct C<T, Ts...>: A<T>, C<Ts...> {};
template<class... Ts> void f(A<Ts>...);
template<class... Ts> std::false_type g(...);
template<class... Ts> decltype(f((A<Ts>(), C<Ts...>())...), std::true_type()) g(int);
template<class... Ts> using distinct = decltype(g<Ts...>(0));
static_assert(distinct<int, char, float>::value, "!!");
static_assert(!distinct<int, char, int>::value, "!!");
THE ERROR
prog.cpp: In instantiation of ‘struct fail<int, int>’:
prog.cpp:23:29: required from here
prog.cpp:9:8: error: duplicate base type ‘dummy<int>’ invalid
struct fail : dummy<T>, dummy<U> {};
As stated in the above diagnostic you cannot explicitly inherit from the same base more than once in a base-specifier-list, and since T and U can possibly be of the same type.. BOOM.
WAIT, HOLD UP; WHAT ABOUT SFINAE?
SFINAE is only checked in the immediate context of the template itself, error that happens beyond the declaration are not suitable to trigger a SFINAE.
14.8.2p8 Template argument deduction [temp.deduct]
If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguemts.
Only invalid types and expressions in the immediate context of the function type and its templat eparameter types can result in a deduction failure.
The ill-formed inheritance does not happen in the immediate context, and the application is ill-formed.
To answer your question explicitly: since inheritance never happens in the declaration of a certain function, the ill-formed inheritance itself cannot be caught by SFINAE.
Sure, you can ask for the compiler to generate a class that uses inheritance, by instantiation it in the function declaration, but the actual (ill-formed) inheritance is not in the immediate context.
As I understand, you want a traits to check if all type are differents,
following may help:
#include <type_traits>
template <typename T, typename ...Ts> struct is_in;
template <typename T> struct is_in<T> : std::false_type {};
template <typename T1, typename T2, typename ... Ts>
struct is_in<T1, T2, Ts...> : std::conditional<std::is_same<T1, T2>::value, std::true_type, is_in<T1, Ts...>>::type {};
template <typename ... Ts> struct are_all_different;
template <> struct are_all_different<> : std::true_type {};
template <typename T> struct are_all_different<T> : std::true_type {};
template <typename T1, typename T2, typename ... Ts> struct are_all_different<T1, T2, Ts...> :
std::conditional<is_in<T1, T2, Ts...>::value, std::false_type, are_all_different<T2, Ts...>>::type {};
static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");
What about a simple std::is_same<>? As far as I can see, it directly models the desired behaviour of your class.
So try something like this:
template<typename T, typename U>
fail
{
fail(const T &t, const U &u)
{
static_assert(std::is_same<T,U>::value,"T and U must have a distinct type");
}
};
Or even better, directly use std::is_same<T,U> in your code.
EDIT: Here is a solution inspired by Jarod42's but which uses only a single class (to make it clearer: this is an answer to the question how to write a variadic class template which detects whether all given types are distinct, which seemed to be the desired goal in one of the early versions of the original question):
#include <type_traits>
template <typename ...Ts> struct are_all_different {};
template <> struct are_all_different<> {static const bool value=true;};
template <typename T> struct are_all_different<T> {static const bool value=true;};
template <typename T1, typename T2>
struct are_all_different<T1, T2>
{
static const bool value = !std::is_same<T1, T2>::value;
};
template <typename T1, typename T2, typename ...Ts>
struct are_all_different<T1,T2,Ts...>
{
static const bool value = are_all_different<T1, T2>::value
&& are_all_different<T1, Ts...>::value
&& are_all_different<T2, Ts...>::value;
};
static_assert(are_all_different<char, int, float, long, short>::value, "type should be all different");
static_assert(!are_all_different<char, int, float, char, short>::value, "type should not all different");
In the end, for n variadic template arguments, this should check for equality of all of the n*(n+1)/2 combinations.
I don’t really understand what you are trying to achieve, but I can help you with the error
template <typename T, typename U>
struct fail : dummy<T>, dummy<U> {};
template <typename T>
struct fail<T, T> : dummy<T> {};
The error is because when you instantiate fail with T and U the same you basically inherit from the same class twice, which is illegal, so you need to create a specialization to take care of this case.