Using std::conditional with is_class<int>, getting compile error - c++

I want to have a type which depends on some other class X having X::value_type.
If there is no such typedef inside the other class, I want to just use X by itself.
So I am looking for code like:
TypeChecker<X>::value_type // Here, value_type might be X or X::value_type, depending on X.
But my initial attempts are failing, see below:
Here's a program that does not compile:
#include<type_traits>
template<typename T>
struct TypeChecker {
typedef typename std::conditional<
std::is_class<T>::value,
typename T::value_type, // I believe the error is due to this line (could be wrong)
T>::type value_type;
};
int main()
{
TypeChecker<int>::value_type x = 3;
return 0;
}
It gives: error: 'int' is not a class, struct, or union type
You can try online: godbolt link
I'm confused, because I thought std::conditional would choose the right branch, but it seems to be evaluating both of them, in some sense. At least, enough to cause a compile error.
Help?

The problem is T::value_type is passed to std::conditional as the template argument, when T is int the expression itself is invalid.
You can do it with class template and partial specialization instead.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, typename std::enable_if<std::is_class<T>::value>::type> {
typedef typename T::value_type value_type;
};
BTW: std::is_class seems to be much loose condition; you can constrain it as has the member type value_type, e.g.
template<typename T, typename = void>
struct TypeChecker {
typedef T value_type;
};
template<typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
typedef typename T::value_type value_type;
};

std::conditional does not "short-circuit". For std::conditional<C,X,Y> to even be instantiated as a class type, the three template arguments must all be actual types. There's no way to have a template that can be used with one of its template parameters representing an illegal or unknown type, even if that parameter doesn't directly matter.
Some solutions to your TypeChecker:
// C++17, using partial specialization SFINAE:
template <typename T, typename Enable = void>
struct TypeChecker {
using value_type = T;
};
template <typename T>
struct TypeChecker<T, std::void_t<typename T::value_type>> {
using value_type = typename T::value_type;
};
// C++11 or later, using function SFINAE:
template <typename T>
struct type_identity { using type = T; };
template <typename T>
type_identity<typename T::value_type> TypeChecker_helper(int); // not defined
template <typename T>
type_identity<T> TypeChecker_helper(...); // not defined
template <typename T>
struct TypeChecker {
using value_type = typename decltype(TypeChecker_helper<T>(0))::type;
};
(Aside: I'm always a bit suspicious of templates that automatically "unwrap" things, like this TypeChecker. It could thwart code which specifically wants to use a container or iterator as a value directly.)

Related

Could pointer_traits typedefs be undefined?

I get stucked on the way of writing my own implementation of STL. The question occurred when I tried to implement std::pointer_traits. By default, std::pointer_traits evaluate his typedef's based on the passed parameter, like follow:
template <typename Ptr>
struct pointer_traits
{
typedef typename Ptr::element_type element_type;
...
};
But here is where the standard says that poiter_traits are ill-formed. When Ptr has no predefined typedef element_type and so on, the compiled error is guaranteed. My implementation offers a new approach. Whenever we try to evaluate typedef's, we will choose between two options:
When Ptr has desired typedef's, we take they as well
When Ptr has not targed typedef's, we will define them as undefined (std::__undefined)
Functions and other stuff are intentionally omitted for simplicity
template <typename Ptr>
class pointer_traits
{
private:
template <typename T, typename = void>
struct __ptr
{
typedef __undefined ptr;
};
template <typename T>
struct __ptr<T, vstl::__void_t<typename T::pointer*>>
{
typedef typename T::pointer ptr;
};
template <typename T, typename = void>
struct __eltype
{
typedef __undefined element_type;
};
template <typename T>
struct __eltype<T, vstl::__void_t<typename T::element_type*>>
{
typedef typename T::element_type element_type;
};
template <typename T, typename = void>
struct __difftype
{
typedef std::ptrdiff_t difference_type;
};
template <typename T>
struct __difftype<T, vstl::__void_t<typename T::difference_type*>>
{
typedef typename T::difference_type difference_type;
};
public:
using pointer = typename __ptr<Ptr>::ptr;
using element_type = typename __eltype<Ptr>::element_type;
using difference_type = typename __difftype<Ptr>::difference_type;
};
This approach will increase pointer_traits stability, but may lead to a misunderstanding. When a type does not provide desired functionality, why we should even instantiate a pointer_traits??? And at a single moment, the state of pointer_traits instance will be undefined for itself. Some members will be defined, some will not.
Does my implementation make sense?

Type trait to receive T::value_type if present, T otherwise

Required is a type trait for a type T providing a typedef type which is of type T::value_type if T has a typedef value_type, T otherwise.
I have tried the following implementation but it does not seem to work (typedef is always of type T, even if T::value_type is present):
template <class T, class = void> struct value_type { using type = T; };
template <class T> struct value_type<T, typename T::value_type> { using type = typename T::value_type; };
template <class T> using value_type_t = typename value_type<T>::type;
std::is_same_v<value_type_t<int>, int> // true
std::is_same_v<value_type_t<std::optional<int>>, int> // false, should be true
Any idea?
The specialization needs to match the base template.
Your base template has class = void, that means the second parameter in your specialization need to be void to match.
The way to do that is to use something like std::void_t, which will become void whatever we put in it. It's only purpose here is to allow SFINAE, if T::value_type is valid we always get void.
template <class T, class = void> struct value_type { using type = T; };
template <class T> struct value_type<T, std::void_t<typename T::value_type>> { using type = typename T::value_type; };
template <class T> using value_type_t = typename value_type<T>::type;

How can I create a value_type type trait?

I am writing a program that will deal heavily with strings of varying types (i.e. std::string, std::string_view, const char*, char[], and friends with varying CharT). So, I am interesting in writing a trait that can abstract getting the value type of generic array-like structures. For the previously listed types, the correct return type would be char.
However, I can not seem to get the implementation correct to allow for generic types. Consider the following attempt:
template<class T>
struct value_type{
using type = std::conditional_t<
std::is_class_v<T>,
typename T::value_type,
std::conditional_t<
std::is_pointer_v<T>,
std::remove_pointer_t<T>,
// etc.
>;
};
This, of course, does not compile because not all types have a nested value_type typedef.
Is there any way to implement this trait effectively?
You might use specialization (and SFINAE):
template<class T, Enabler = void>
struct value_type;
template<class T>
struct value_type<T, std::void_t<typename T::value_type>>
{
using type = typename T::value_type;
};
template<class T>
struct value_type<const T*>
{
using type = T;
};
template<class T, std::size_t N>
struct value_type<const T[N]>
{
using type = T;
};
// ...
You can defer to a helper class template. Fortunately type aliases aren't instantiated during an implicit instantiation:
template <class T>
struct get_value_type {
using type = typename T::value_type;
};
template <class T>
struct value_type {
using type = typename std::conditional_t<
std::is_class_v<T>,
get_value_type<T>,
std::conditional<std::is_pointer_v<T>, std::remove_pointer_t<T>, void>
>::type;
};
This will also require you to change the second std::conditional_t to std::conditional so that we can get ::type from whatever type is returned.

Using `std::conditional_t` to define a class' `typedef` in dependence of its template parameter

I try to use std::conditional_t to define a class A's typedef in dependence of its template parameter T:
template< typename T >
class A
{
public:
typedef std::conditional_t< std::is_fundamental<T>::value, T, decltype(std::declval<T>().foo())> type;
};
template< typename T >
class B
{
public:
T foo()
{
// ...
}
};
int main()
{
typename A< int >::type a = 5; // causes an error
typename A< B<int> >::type b = 5; // does not cause an error
return 0;
}
Unfortunately, the code does not compile. Error:
error: member reference base type 'int' is not a structure or union
typedef std::conditional_t< std::is_fundamental<T>::value, T, decltype(std::declval<T>().foo())> type;
Does anyone know how to fix it?
Both types in the conditional expression must be valid, this isn't a SFINAE context.
You can achieve what you want by "executing" only the chosen class template:
typedef typename std::conditional_t< std::is_fundamental<T>::value, identity<T>, foo_t<T>>::type type;
with those defined as:
template<typename T>
struct identity{ using type = T; };
template<typename T>
struct foo_t{ using type = decltype(std::declval<T>().foo()); };
demo
The compiler need to instantiate the whole thing when you ask for ::type. With ints, it's like asking the compiler to give you that type:
std::conditional_t<true, int, decltype(std::declval<int>().foo())>
But unfortunately, this is ill formed. You'll need to ask the compiler to do some sfinae to choose the right type:
template<typename T, typename = void>
struct A {
using type = T;
};
template<typename T>
struct A<T, void_t<decltype(std::declval<T>().foo())>> {
using type = decltype(std::declval<T>().foo());
};
The compiler will choose the second specialization if possible. In other words, if the type T really has .foo() possible, then type will be equal to the type of that expression.
You can implement void_t like that:
template<typename...>
using void_t = void;
The advantage of this silution is that it don't work only with fundamentals, but the compiler will choose the first version if it can't find a .foo() function in the type. You could even add a third version:
template<typename T>
struct A<T, void_t<decltype(std::declval<T>().bar())>> {
using type = decltype(std::declval<T>().bar());
};
Now your struct works with types that have .bar() too.
template<template<class...>class Z>
struct wrap_z{
template<class...Ts>
using result=Z<Ts...>;
};
template<bool b, template<class...>class T, template<class...>class F, class...Ts>
using conditional_apply =
std::conditional_t<
b, wrap_z<T>, wrap_z<F>
>::template result<Ts...>;
template<class T>using identity = T;
now we use it:
public:
template<class T>using do_foo = decltype(std::declval<T>().foo());
using type = conditional_apply< std::is_fundamental<T>{}, identity, do_foo, T>;
which I think is clearer at point-of-use than alternatives.

Template specialization to use default type if class member typedef does not exist

I'm trying to write code that uses a member typedef of a template argument, but want to supply a default type if the template argument does not have that typedef. A simplified example I've tried is this:
struct DefaultType { DefaultType() { printf("Default "); } };
struct NonDefaultType { NonDefaultType() { printf("NonDefault "); } };
struct A {};
struct B { typedef NonDefaultType Type; };
template<typename T, typename Enable = void> struct Get_Type {
typedef DefaultType Type;
};
template<typename T> struct Get_Type< T, typename T::Type > {
typedef typename T::Type Type;
};
int main()
{
Get_Type<A>::Type test1;
Get_Type<B>::Type test2;
}
I would expect this to print "Default NonDefault", but instead it prints "Default Default". My expectation is that the second line in main() should match the specialized version of Get_Type, because B::Type exists. However, this does not happen.
Can anyone explain what's going on here and how to fix it, or another way to accomplish the same goal?
Thank you.
Edit:
Georg gave an alternate method, but I'm still curious about why this doesn't work. According the the boost enable_if docs, a way to specialize a template for different types is like so:
template <class T, class Enable = void>
class A { ... };
template <class T>
class A<T, typename enable_if<is_integral<T> >::type> { ... };
template <class T>
class A<T, typename enable_if<is_float<T> >::type> { ... };
This works because enable_if< true > has type as a typedef, but enable_if< false > does not.
I don't understand how this is different than my version, where instead of using enable_if I'm just using T::Type directly. If T::Type exists wouldn't that be the same as enable_if< true >::type in the above example and cause the specialization to be chosen? And if T::Type doesn't exist, wouldn't that be the same as enable_if< false >::type not existing and causing the default version to be chosen in the above example?
To answer your addition - your specialization argument passes the member typedef and expects it to yield void as type. There is nothing magic about this - it just uses a default argument. Let's see how it works. If you say Get_Type<Foo>::type, the compiler uses the default argument of Enable, which is void, and the type name becomes Get_Type<Foo, void>::type. Now, the compiler checks whether any partial specialization matches.
Your partial specialization's argument list <T, typename T::Type> is deduced from the original argument list <Foo, void>. This will deduce T to Foo and afterwards substitutes that Foo into the second argument of the specialization, yielding a final result of <Foo, NonDefaultType> for your partial specialization. That doesn't, however, match the original argument list <Foo, void> at all!
You need a way to yield the void type, as in the following:
template<typename T>
struct tovoid { typedef void type; };
template<typename T, typename Enable = void> struct Get_Type {
typedef DefaultType Type;
};
template<typename T>
struct Get_Type< T, typename tovoid<typename T::Type>::type > {
typedef typename T::Type Type;
};
Now this will work like you expect. Using MPL, you can use always instead of tovoid
typename apply< always<void>, typename T::type >::type
You can do that by utilizing SFINAE:
template<class T> struct has_type {
template<class U> static char (&test(typename U::Type const*))[1];
template<class U> static char (&test(...))[2];
static const bool value = (sizeof(test<T>(0)) == 1);
};
template<class T, bool has = has_type<T>::value> struct Get_Type {
typedef DefaultType Type;
};
template<class T> struct Get_Type<T, true> {
typedef typename T::Type Type;
};
First step: stop using "Type" and use the mpl standard "type".
BOOST_MPL_HAS_XXX_DEF(Type)
template < typename T >
struct get_type { typedef typename T::Type type; };
template < typename T >
struct calculate_type : boost::mpl::if_
<
has_Type<T>
, get_type<T>
, boost::mpl::identity<default_type>
>::type {}
typedef calculate_type<A>::type whatever;
If you used "type" instead of "Type" in your metafunctions you wouldn't require the fetcher "get_type" to convert it and could just return T in that case.