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>;
// ~~~~~~~~~^
Related
As a contrived example, let's say I want to write a type that can add a new template template parameter to a type that has a variadic template template parameter pack:
namespace
{
template <template <typename...> typename... T>
struct foo {};
template <typename Original, template <typename...> typename T>
class add_to_foo {
template <template <typename...> typename... V>
struct appender {
using type = foo<V..., T>;
explicit appender(foo<V...>) {}
};
template <template <typename...> typename... V>
appender(foo<V...>) -> appender<V...>;
public:
using type = typename decltype(appender(std::declval<Original>()))::type;
};
}
int main() {
using original_foo = foo<std::vector, std::basic_string>;
using new_foo = typename add_to_foo<original_foo, std::deque>::type;
static_assert(std::is_same_v<new_foo, foo<std::vector, std::basic_string, std::deque>>);
return EXIT_SUCCESS;
}
It works, but it's limited in that appender only works with foo rather than a generic template parameter. Let's try to replace foo with a template template parameter:
namespace
{
template <template <typename...> typename... T>
struct foo {};
template <typename Original, template <typename...> typename T>
class add_to_u {
template <template <typename...> typename U, template <typename...> typename... V>
struct appender {
using type = U<V..., T>;
explicit appender(U<V...>) {}
};
template <template <typename...> typename U, template <typename...> typename... V>
appender(U<V...>) -> appender<V...>;
public:
using type = typename decltype(appender(std::declval<Original>()))::type;
};
}
int main() {
using original_foo = foo<std::vector, std::basic_string>;
using new_foo = typename add_to_u<original_foo, std::deque>::type;
static_assert(std::is_same_v<new_foo, foo<std::vector, std::basic_string, std::deque>>);
return EXIT_SUCCESS;
}
This doesn't work, there are lot's of errors, but the main ones are:
<source>:14:25: error: use of template template parameter 'V' requires template arguments
using type = U<V..., T>;
<source>:23:36: error: no viable constructor or deduction guide for deduction of template arguments of 'appender'
using type = typename decltype(appender(std::declval<Original>()))::type;
It looks like the compiler thinks that the template arguments of U need to be complete types rather than template template parameters, this isn't the case when foo was used presumably because the compiler could see that it used template template parameters. So I need to tell the compiler that U is a template template parameter whose parameters are also template template parameters - any idea how?
I have the following class:
template <typename T, typename U = UDefault>
class A;
How to check whether types such as A<float> or A<float, int> are instances of the above templated class?
I tried modifying How to check if an object is an instance of a template class in C++? into:
template <typename T, typename U>
struct IsA : std::false_type
{};
template <typename T, typename U>
struct IsA<A<T, U>> : std::true_type
{};
but it's giving the following error
20:18: error: wrong number of template arguments (1, should be 2)
16:8: error: provided for 'template<class T, class U> struct IsA'
In function 'int main()':
40:34: error: wrong number of template arguments (1, should be 2)
16:8: error: provided for 'template<class T, class U> struct IsA'
How can this be solved?
Your IsA class should be expected to take one template argument. The type you are testing.
template <typename Type>
struct IsA : std::false_type {};
template <typename T, typename U>
struct IsA< A<T,U> > : std::true_type {};
// ^^^^^^ The specialization of your one template argument.
Or put alternately, since the individual template parameters to A do not matter here:
template <typename ...AParams>
struct IsA< A<AParams...> > : std::true_type {};
// ^^^^^^^^^^^^^ The specialization of your one template argument.
See it work in Compiler Explorer
This compiles fine without errors on both clang and gcc, but fails with MSVC: (compiler explorer)
// detection idiom
template <class...>
using void_t = void;
template <class Void, template <class...> class Op, class... Args>
struct detector_base
{
using type = void;
};
template <template <class...> class Op, class... Args>
struct detector_base<void_t<Op<Args...>>, Op, Args...>
{
using type = Op<Args...>;
};
template <class T>
using check = decltype(T::x);
template <typename T>
struct type {};
using dt = typename detector_base<void, check, int>::type; // this is void
using t = type<dt>;
// ^--- 't': use of class template requires template argument list
This occurs when using the detected type with any template type. Is this a compiler bug, or is due to lack of support (if so, for what)?
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;
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.