Search code examples
c++metaprogrammingtemplate-meta-programmingoperator-precedenceevaluation-strategy

What is the evaluation strategy (eager, lazy, ...) of C++ metafunctions such as std::conditional?


C++14 draft n4140 reads

T shall be an enumeration type

for template <class T> struct underlying_type.

How bad is it to write

std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>

when T can be an arbitrary type? Will I step onto an UB and will the compiler delete my $HOME (because language lawyers say "anything can happen under an UB") ?


Solution

  • Will I step onto an UB [...]

    Technically, yes. But practically, it just won't compile for non-enumeration types. When you write:

    std::conditional_t<std::is_enum<T>::value, std::underlying_type_t<T>, foo>;    
                                               ^^^^^^^^^^^^^^^^^^^^^^^^^
    

    That template parameter must be evaluated before the conditional template can be instantiated. This is equivalent to all function arguments having to be invoked before the body of the function begins. For non-enumerated types, underlying_type<T> is incomplete (sure it's specified as undefined in the standard but let's be reasonable), so there is no underlying_type_t. So the instantiation fails.

    What you need to do is delay the instantiation in that case:

    template <class T> struct tag { using type = T; };
    
    typename std::conditional_t<
        std::is_enum<T>::value,
        std::underlying_type<T>,
        tag<foo>>::type;
    

    Now, our conditional instead of selecting types is selecting a metafunction! underlying_type<T>::type will only be instantiated for T being an enum. We additionally have to wrap foo to turn it into a metafunction.

    This is a common pattern and was a special thing in Boost.MPL called eval_if, which would look like:

    template <bool B, class T, class F>
    using eval_if_t = typename std::conditional_t<B, T, F>::type;
    

    Note that we're both using conditional_t and ::type.