1

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") ?

nodakai
  • 7,773
  • 3
  • 30
  • 60
  • 2
    UB is something that happens at runtime, template functions such as this do their work at compile time. As for "will it delete $HOME" see [Chandler's talk](https://www.youtube.com/watch?v=yG1OZ69H_-o) – Borgleader Dec 21 '16 at 16:47
  • This is compile-time evaluation, not run-time. You won't run into UB here. – AndyG Dec 21 '16 at 16:48
  • @Borgleader OK "will the resulting binary possibly delete my $HOME" ? Actually I'm not even sure if it constitutes an UB to write `std::underlying_type_t` straight (w/o `std::conditional`.) Does "shall" there mean its violation leads to an ill-formed program? – nodakai Dec 21 '16 at 16:53
  • @nodakai: [cppreference](http://en.cppreference.com/w/cpp/types/underlying_type) states it pretty well: "If T is a complete enumeration type, provides a member typedef type that names the underlying type of T. Otherwise, the behavior is undefined.". Edit: So don't try to use `std::underlying_type_t` – AndyG Dec 21 '16 at 16:57
  • @AndyG I would not have posted this question if I could 100 % trust a community-driven wiki. I could not easily find any statements in n4140 which supports the last statement in your quote. – nodakai Dec 21 '16 at 17:04

1 Answers1

6

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.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • OK, so C++ metafunctions employ eager evaluation (if we don't use your "tag" trick.) Your post is very educating but I think I need a bit more time to grasp it. – nodakai Dec 21 '16 at 17:09
  • @nodakai Think of it as - template arguments are to metafunctions what function arguments are to functions. – Barry Dec 21 '16 at 17:45