user877329
user877329

Reputation: 6200

std::conditional_t for class type vs non-class type

How to fix this:

template<class T>
struct ResultType
{
    using type = std::conditional_t<std::is_class_v<T>, typename T::result_type, void>;
};

It cannot be it is supposed to return void, if T is not class type, but instead:

error: ‘int’ is not a class, struct, or union type 24 | using type = std::conditional_tstd::is_class_v<T, typename T::result_type, void>;

So I need to not try to invoke the false expression, but how?

Upvotes: 2

Views: 1145

Answers (3)

Bitwize
Bitwize

Reputation: 11220

The failure is because std::conditional selects one of the two type expressions -- but by this point the type expressions are already evaluated. Since int is not a class, and does not have result_type -- it errors.

As others have pointed out, this can be resolved with SFINAE via enable_if or void_t -- but one other approach is to leverage function overloads with expression SFINAE instead of requiring partial specializations:

template <typename T, typename Default = void>
class ResultType
{
    static auto test(...) -> Default;
    template <typename U>
    static auto test(const U&) -> typename U::result_type;
public:
    using type = decltype(test(std::declval<T>()));
};

Live Example

When T is a type that defines result_type, the test(const U&) branch is enabled and is selected for overload resolution; otherwise test(...) is selected for everything else and becomes Default (void, in this case).

The type is then deduced with decltype by evaluating the expression to see which overload gets selected.

Upvotes: 0

463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 122458

std::conditional_t is to select between two types, but when T = int then T::result_type is not a type. You can use sfinae:

#include <type_traits>

template <typename T, typename = void>
struct result_type_or_void {
    using type = void;
};
template <typename T>
struct result_type_or_void<T,std::void_t<typename T::result_type>> {
    using type = typename T::result_type;
};

template<class T>
struct ResultType
{
    using type = typename result_type_or_void<T>::type;
};

struct Test {
    using result_type = int;
};

int main() {
    ResultType<int> t;
    static_assert( std::is_same_v<ResultType<int>::type,void>);
    static_assert( std::is_same_v<ResultType<Test>::type,int>);
}

Upvotes: 3

Ruks
Ruks

Reputation: 3956

In the following:

using type = std::conditional_t<std::is_class_v<T>, typename T::result_type, void>;

The part typename T::result_type will fail when T = int, because typename int::result_type is ill-formed.

You can fix this by using a template specialization instead of std::conditional which does the exact same thing but avoids doing T::result_type when T is not a class type:

#include <type_traits>

template <typename T, typename = void>
struct ResultType;

template <typename T>
struct ResultType<T, std::enable_if_t<!std::is_class_v<T>>> {
    using type = void;
};

template<typename T>
struct ResultType<T, std::enable_if_t<std::is_class_v<T>>> {
    using type = typename T::result_type;
};

// ...

struct X {
    using result_type = int;
};

int main() {
    static_assert(std::is_same_v<typename ResultType<X>::type, typename X::result_type>, "FAIL!");
    static_assert(std::is_same_v<typename ResultType<int>::type, void>, "FAIL!");
}

Upvotes: 7

Related Questions