Templates: SFINAE specialization not getting applied - c++

The below snippet is, at least I thought, as straightward an example as possible of SFINAE applied to specialization.
The last line is the main point, and where the failure is occurring. The definition of a specialized foo template determines the specialization of the bar template, or so I want. Other bar specializations can be defined elsewhere, or perhaps the use of arbitary types can simply remain unsupported.
The same pattern is widely suggested for use with enable_if, as I understand.
template <typename T>
struct foo;
template <>
struct foo<int> {
using type = int;
};
template <typename T, typename use = void>
struct bar;
template <typename T>
struct bar<T, typename foo<T>::type> {
using type = typename foo<T>::type;
};
using good = typename foo<int>::type;
using bad = typename bar<int>::type;
In g++, with 14 or 17 standard, the result is shown below. It seems that the bar specialization is not getting applied, and the compiler is using the unspecialized (empty) definition. Why?
$ g++ --std=c++14 special.cpp -o special
special.cpp:18:32: error: ‘type’ in ‘struct bar<int>’ does not name a type
using bad = typename bar<int>::type;

template <typename T>
struct bar<T, typename foo<T>::type> {
using type = typename foo<T>::type;
};
should be
template <typename T>
struct bar<T, std::void_t<typename foo<T>::type>> {
using type = typename foo<T>::type;
};
as use should always be void.
That's why naming as AlwaysVoid would be better.
(or use its role as something like Enabler)
template <typename T, typename AlwaysVoid = void>
struct bar;

Related

What is wrong with my application of SFINAE when trying to implement a type trait?

I needed a type trait that decays enums to their underlying type, and works the same as decay_t for all other types. I've written the following code, and apparently this is not how SFINAE works. But it is how I thought it should work, so what exactly is the problem with this code and what's the gap in my understanding of C++?
namespace detail {
template <typename T, std::enable_if_t<!std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T, std::enable_if_t<std::is_enum_v<T>>* = nullptr>
struct BaseType {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
The error in MSVC is completely unintelligible:
'detail::BaseType': template parameter '__formal' is incompatible with
the declaration
In GCC it's a bit better - says that declarations of the second template parameter are incompatible between the two BaseType templates. But according to my understanding of SFINAE, only one should be visible at all for any given T and the other one should be malformed thanks to enable_if.
Godbolt link
SFINAE applied to class templates is primarily about choosing between partial specialisations of the template. The problem in your snippet is that there are no partial specialisations in sight. You define two primary class templates, with the same template name. That's a redefinition error.
To make it work, we should restructure the relationship between the trait implementations in such as way that they specialise the same template.
namespace detail {
template <typename T, typename = void> // Non specialised case
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, std::enable_if_t<std::is_enum_v<T>>> {
using Type = std::underlying_type_t<T>;
};
}
template <class T>
using base_type_t = typename detail::BaseType<T>::Type;
The specialisation provides a void type for the second argument (just like the primary would be instantiated with). But because it does so in a "special way", partial ordering considers it more specialised. When substitution fails (we don't pass an enum), the primary becomes the fallback.
You can provide as many such specialisation as you want, so long as the second template argument is always void, and all specialisations have mutually exclusive conditions.
BaseType isn't being partial-specialized, you're just redeclaring it, and since the non-type parameter has a different type, the compilation fails. you might want to do
#include <type_traits>
namespace detail {
template <typename T, bool = std::is_enum_v<T>>
struct BaseType;
template <typename T>
struct BaseType<T, false> {
using Type = std::decay_t<T>;
};
template <typename T>
struct BaseType<T, true> {
using Type = std::underlying_type_t<T>;
};
}
You declare the same struct with different parameter, which is forbidden.
You can do it with partial specialization:
namespace detail {
template <typename T, typename Enabler = void>
struct BaseType {
using Type = std::decay_t<T>;
};
template <typename E>
struct BaseType<E, std::enable_if_t<std::is_enum_v<E>>>
{
using Type = std::underlying_type_t<E>;
};
}
Demo

C++ SFINAE partial specialization

I've been trying to define an auxiliary class to help me work with template methods in which I would like a generic implementation both for complex and real types.
That has been my attempt so far:
#include<type_traits>
#include<complex>
template<class T>
struct is_complex{ static constexpr bool value = false;};
template<class T>
struct is_complex<std::complex<T>> :
std::integral_constant<bool,
std::is_integral<T>::value ||
std::is_floating_point<T>::value>{};
template<class T>
struct is_arithmetic:
std::integral_constant<bool,
std::is_integral<T>::value ||
std::is_floating_point<T>::value ||
is_complex<T>::value>{};
template<class T,
typename std::enable_if_t<is_arithmetic<T>::value,int> =0>
struct real_type {typedef T type;};
template<class T>
struct real_type<typename std::complex<T>>{typedef T type;};
I want to get something like
typename real_type<std::complex<double>> myVar1;//myVar1 is double
typename real_type<double> myVar2;//myVar2 is double
I was able to make it work as long as I didn't care that non-arithmetic types also had real_type<T>::type. But now that I have added this additional constraint, I cannot make it work and I don't really see why.
To clarify: I would like that calls like real_type<std::string>::type would generate compile-time errors. I want these calls to be valid only to arithmetic (including complex) and integral types.
The compiler error of my latest attempt was:
non-type template argument specializes a template parameter with dependent type 'typename std::enable_if_t<is_arithmetic<T>::value, int>' (aka 'typename enable_if<is_arithmetic<T>::value, int>::type')
But I don't know how to deal with it. If this information is useful, I have access to compilers supporting C++17.
Usually this is done with specialization and a template defaulted parameter.
I mean
template <typename, typename = void>
struct real_type;
template <typename T>
struct real_type<T, std::enable_if_t<std::is_arithmetic_v<T>>>
{ using type = T; };
template <typename T>
struct real_type<std::complex<T>, void>
{ using type = T; };
where you have a separate specialization for std::complex and, as observed by Patrick Roberts (thanks), without std::complex your is_arithmetic become a duplicate of std::is_arithmetic (so is better directly use std::is_arithmetic).
You get
real_type<int> r1; // compile
real_type<std::complex<float>> r2; // compile
//real_type<std::string> r3; // compilation error
May I suggest a slightly more concise implementation that works more generally (which requires c++17):
#include <type_traits>
// catch all for single parameters that have 0 template parameters
template <typename T>
struct real_type{
static_assert(std::is_arithmetic_v<T>);
using type = T;
};
template <typename T>
using real_type_t = typename real_type<T>::type;
// magically catch anything which matches V<T, Ts...> and exposes T as `type`
template <template <typename...> typename V, typename T, typename...Ts>
struct real_type<V<T, Ts...>>{
using type = real_type_t<T>;
};
#include <vector>
#include <complex>
using d = real_type_t<double>;
static_assert(std::is_same_v<d, double>);
using d2 = real_type_t<std::vector<double>>;
static_assert(std::is_same_v<d2, double>);
using d3 = real_type_t<std::complex<double>>;
static_assert(std::is_same_v<d3, double>);
// doesn't compile
struct NotValid {};
using d4 = real_type_t<std::vector<NotValid>>;

alias template is not a class template?

I am trying to write a type trait to detect if a type has a T::type of certain type. I am using code from this answer. For reference this is the part of the code I am using:
// See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4502.pdf.
template <typename...>
using void_t = void;
// Primary template handles all types not supporting the operation.
template <typename, template <typename> class, typename = void_t<>>
struct detect : std::false_type {};
// Specialization recognizes/validates only types supporting the archetype.
template <typename T, template <typename> class Op>
struct detect<T, Op, void_t<Op<T>>> : std::true_type {};
I started simple with a trait to detect the T::type :
template <typename T>
using has_type_t = typename T::type;
template <typename T>
using has_type = detect<T, has_type_t>;
This works as expected, but when I ask for the actual type of T::type also I get errors that I do not understand:
template <typename X>
struct has_X_type_helper {
template <typename T>
using type = typename std::enable_if_t<std::is_same_v< typename T::type, X>,int>;
};
template <typename T,typename X>
using has_X_type = detect<T,has_X_type_helper<X>::type>;
GCC:
<source>:49:55: error: type/value mismatch at argument 2 in template parameter list for 'template<class, template<class> class<template-parameter-1-2>, class> struct detect'
49 | using has_X_type = detect<T,has_X_type_helper<X>::type>;
| ^
<source>:49:55: note: expected a class template, got 'has_X_type_helper<X>::type'
and Clang is even more confusing for me
<source>:49:29: error: template argument for template template parameter must be a class template or type alias template
using has_X_type = detect<T,has_X_type_helper<X>::type>;
^
Is has_X_type_helper<X>::type not a type alias template ? What is wrong in my code?
# godbolt
You need to indicate that the nested thing is a template:
template <typename T, typename X>
using has_X_type = detect<T, has_X_type_helper<X>::template type>;
// ~~~~~~~~~^

How the when<> trait in boost.Hana works?

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.

Use enable_if with is_integral to make distribution traits

I want to make a traits for std::uniform_*_distribution according to type given. E.g.:
distribution_traits<float>::type int_dist;
I tried following ways, but none of them compiles, and I don't know why.
Implementation 1
Use std::enable_if with typedefs:
template <typename T>
struct distribution_traits {
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
};
Clang 3.4 complains:
dist_traits.cpp:7:9: error: redefinition of 'type'
using type = typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
^
dist_traits.cpp:6:9: note: previous definition is here
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
^
dist_traits.cpp:6:40: error: no type named 'type' in 'std::enable_if<false, std::uniform_int_distribution<float> >'; 'enable_if' cannot be used to
disable this declaration
using type = typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
^~~~~~~~~~~~~~~~~~~~~~~~~~
dist_traits.cpp:28:3: note: in instantiation of template class 'distribution_traits<float>' requested here
distribution_traits<float>::type int_dist;
^
2 errors generated.
Implementation 2
Use enable_if as class template parameter:
template <typename T, typename distribution_t = void>
struct distribution_traits;
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_integral<T>::value,
std::uniform_int_distribution<T> >::type > {
using type = std::uniform_int_distribution<T>;
};
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_floating_point<T>::value,
std::uniform_real_distribution<T> >::type > {
using type = std::uniform_real_distribution<T>;
};
And Clang complains
dist_traits.cpp:28:3: error: implicit instantiation of undefined template 'distribution_traits<float, void>'
distribution_traits<float>::type int_dist;
^
Either way cannot be compiled by MSVC++ 12.0, and the error messages are similar.
Could anyone please explain what's wrong I'm doing with SFINAE? Thanks!
For those who are curious about solution, here is the one that compiles:
template <typename T>
auto dist() -> typename std::enable_if<std::is_integral<T>::value, std::uniform_int_distribution<T>>::type;
template <typename T>
auto dist() -> typename std::enable_if<std::is_floating_point<T>::value, std::uniform_real_distribution<T>>::type;
template <typename T>
struct distribution_traits {
using type = decltype(dist<T>());
};
BTW, if put dist function into distribution_traits, the compilation will fail with error: function only differs in return type cannot be overloaded. :(
SFINAE can be used to discard overloads of function templates and class template specializations during substitution of template arguments.
It cannot be used with type/template aliases like you're trying to do.
About your working code - putting dist inside the class doesn't work because you attempt to call dist inside decltype without an object. Make dist static and it'll work:
template <typename T>
struct distribution_traits {
template <typename U>
static auto dist() -> typename std::enable_if<std::is_integral<U>::value, std::uniform_int_distribution<U>>::type;
template <typename U>
static auto dist() -> typename std::enable_if<std::is_floating_point<U>::value, std::uniform_real_distribution<U>>::type;
using type = decltype(dist<T>());
};
For implementation 2 to work, you need to omit the second argument of enable_if:
template
struct distribution_traits;
template <typename T>
struct distribution_traits<
T, typename std::enable_if<std::is_integral<T>::value>::type> {
using type = std::uniform_int_distribution<T>;
};
otherwise the specialization you define is distribution_traits<T, uniform_int_distribution<T>> and that doesn't match an instantiation like distribution_traits<float> because the second parameter is defaulted to void.