Is it possible to build a lazy conditional metafunction [duplicate]
Asked Answered
P

1

8

Suppose I want to use std::conditional to determine a type, if the type is a vector<...> the return will be a vector<...>::size_type and if not it will be int. (just an example).

A naive way to use std::conditional:

template<class V> struct is_vector : std::false_type{};
template<class T> struct is_vector<std::vector<T>> : std::true_type{};

template<class C>
using my_size_type = typename std::conditional<
    not is_vector<C>::value, 
    int, 
    C::size_type // note that this line only makes sense when condition is false
>::type;

However this fails because if C is say a double, double::size_type will give an error, even if the that is the evaluation of the second false option.

So, I am wonder if there is a sort of lazy_conditional in which the false (or the second false) statement is not evaluated.

I found something here: https://mcmap.net/q/1093881/-lazy-evaluation but I don't know how to use it my example.


Note that I know how to get the same result without using std::conditional:

template<class V> struct my_size_type{typedef int type;};
template<class T> struct my_size_type<std::vector<T>>{typedef std::vector<T>::size_type type;};

The question is if there is a lazy_conditional that somehow encapsulated a std::conditional that is short circuited.


After some trial error I manage to use the ideas in https://mcmap.net/q/1093881/-lazy-evaluation and get to this that follows. It also makes me think that it is not possible to write std::lazy_conditional because C::size_type cannot appear at all in any expression a priori, so two-step expressions are needed.

template<class C, bool B> struct false_case{
    typedef void type;
};
template<class C> struct false_case<C, false>{
    typedef typename C::size_type type;
};

template<class C>
using size_type = typename std::conditional<
    not is_vector<C>::value, 
    int, 
    typename false_case<C, not is_vector<C>::value>::type
>::type;

I couldn't even condense this into a macro, because each case is different.

Pasquil answered 15/12, 2015 at 4:15 Comment(0)
U
7

You need a level of indirection.

template<class T> struct identity { using type = T; };

template<class C> 
struct size_type_of : identity<typename C::size_type> { };

template<class C>
using size_type = typename std::conditional<not is_vector<C>::value,
                                            identity<int>,
                                            size_type_of<C>>::type::type;

The point is to delay looking at C::size_type (by instantiating size_type_of<C>) until you know it has one.


If what you really want to do is "C::size_type if it exists, int otherwise", then std::experimental::detected_or_t is your friend:

template<class C>
using size_type_t = typename C::size_type;

template<class C>
using size_type_or_default = std::experimental::detected_or_t<int, size_type_t, C>;
Unbelieving answered 15/12, 2015 at 6:29 Comment(3)
Ok, and I guess it can't be encapsulated as a lazy_conditional<C, T, lazy_F>. Anyway this also shows that free metafunction are better than member (have std::size_type_of<T>::type rather than rely on T::size_type) when possible (like free function is better than member functions for generic code).Pasquil
ah, this is something I discover over and over again, at the end it is the same I did here: #5839857Pasquil
On detected_or_t compatibility: #36419070Pasquil

© 2022 - 2024 — McMap. All rights reserved.