Search code examples
c++templatesc++11conditional-statementsshort-circuiting

Is it possible to build a lazy conditional metafunction


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://stackoverflow.com/a/5317659/225186 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://stackoverflow.com/a/5317659/225186 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.


Solution

  • 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>;