Using enable_if and underlying_type in function signature in VS2012

1.4k views Asked by At

This code works in VS2013 and other compilers (tested clang 3.4 and gcc 4.8) but fails to compile in VS2012:

#include <type_traits>
#include <cstdio>

// error C4519: default template arguments are only allowed on a class template

template<typename E, typename std::enable_if<std::is_enum<E>::value>::type* = nullptr>
typename std::underlying_type<E>::type to_integral(E e)
{
    return static_cast<typename std::underlying_type<E>::type>(e);
}

template<typename E, typename std::enable_if<!std::is_enum<E>::value>::type* = nullptr>
E to_integral(E e)
{
    return e;
}

enum class MyEnum : int { A = 5 };

int main()
{
    auto a = to_integral(42);
    auto b = to_integral(MyEnum::A);
    printf("%d\n", a);
    printf("%d\n", b);
}

How can I write to_integral in VS2012? Is it possible? I tried using enable_if on the return argument and as a parameter but then the underlying_type appears in the function signature which compilers tend not to like for non-enum types.

2

There are 2 answers

14
Yakk - Adam Nevraumont On BEST ANSWER

Put the enable_if in the return type:

template<bool b, template<class>class X, class T>
struct invoke_if {};

template<template<class>class X, class T>
struct invoke_if<true, X, T> {
  typedef typename X<T>::type type;
};

template<typename E>
typename invoke_if< std::is_enum<E>::value,std::underlying_type, E >::type
to_integral(E e) {
  return static_cast<typename std::underlying_type<E>::type>(std::move(e));
}

or the simpler:

template<typename E>
typename std::enable_if< std::is_enum<E>::value,std::underlying_type<E> >::type::type
to_integral(E e) {
  return static_cast<typename std::underlying_type<E>::type>(std::move(e));
}

for the first specialization. For the second, I'd recommend:

template<typename E>
typename std::enable_if<!std::is_enum<E>::value&&std::is_integral<E>::value,E>::type
to_integral(E e) {
  return std::move(e);
}

should work in MSVC2012 live example. Note the extra condition, and the std::move (just in case you have a bigint class that qualifies as is_integral). (it is usually allowed to specialize such traits in std). It also means that if you call to_integral(3.14) you get an error, which I think is good.

Oh, and template<bool b, class T=void>using enable_if_t=typename std::enable_if<b,T>::type; can save a lot of typename spam (however, 2012 either has lack of support, and 2013 has flaky support, for this kind of thing).

1
Ben Hymers On

Here's my stab at wrapping it up in a struct, which VS2012 is happy with. I doubt it's the smartest implementation but it's working for my test case. If someone else submits something nicer though I'll happily accept it! Also I nicked @Yakk's idea of using std::move.

Working in clang/GCC and VS2013, and can't find an online VS2012 compiler but it's working locally.

#include <type_traits>
#include <cstdio>

template<class E, class Enable = void>
struct to_integral_helper
{
    static E inner(E e)
    {
        return std::move(e);
    }
};

template<typename E>
struct to_integral_helper<E, typename std::enable_if<std::is_enum<E>::value>::type>
{
    static typename std::underlying_type<E>::type inner(E e)
    {
        return static_cast<typename std::underlying_type<E>::type>(std::move(e));
    }
};

template<typename E>
auto to_integral(E e) -> decltype(to_integral_helper<E>::inner(e))
{
    return to_integral_helper<E>::inner(std::move(e));
}

enum class MyEnum { A = 5 };

int main()
{
    auto a = to_integral(42);
    auto b = to_integral(MyEnum::A);
    printf("%d\n", a);
    printf("%d\n", b);
}