0

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.

Ben Hymers
  • 25,586
  • 16
  • 59
  • 84

2 Answers2

1

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).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Although this may work with MSVS 2012, it doesn't on [Clang](http://coliru.stacked-crooked.com/a/2505d4049519655d)/[GCC](http://coliru.stacked-crooked.com/a/bf083b5376cbcfd7)... – rubenvb Dec 01 '14 at 16:11
  • The substitution failure is not in the immediate context of the function template declaration. – Columbo Dec 01 '14 at 16:13
  • @rubenvb fixed in Clang. Probably GCC. – Yakk - Adam Nevraumont Dec 01 '14 at 16:17
  • @Columbo ya, missed that `underlying_type` is not SFINAE friendly. Used a `defer`/`apply` strategy to bundle it up and apply it in a SFINAE friendly way. Could probably write it cleaner too... – Yakk - Adam Nevraumont Dec 01 '14 at 16:18
  • @Yakk There is no need for a defer template. `std::enable_if::value, std::underlying_type>::type::type`. – Columbo Dec 01 '14 at 16:18
  • VS2012 doesn't have variadic templates either :( – Ben Hymers Dec 01 '14 at 16:20
  • @Columbo I am less certain that `templatestruct underlying_type;` can be expanded without a valid specialization. I know most compilers will do it, but I'm leery. – Yakk - Adam Nevraumont Dec 01 '14 at 16:22
  • @Yakk If `E` isn't an enumeration, the specialization should not be instantiated. Or what do you mean by "expanded"? – Columbo Dec 01 '14 at 16:23
  • @BenHymers `...`s removed, not needed for this case anyhow. – Yakk - Adam Nevraumont Dec 01 '14 at 16:23
  • `std::underlying_type` is still named (and passed to `std::enable_if`) for an `E` not an `enum`. Do you have the chapter and verse that says that is legal? – Yakk - Adam Nevraumont Dec 01 '14 at 16:24
  • @Yakk [temp.inst]: "[…] the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program". Clearly the complete type is not needed in the primary specialization (or whatever) that is matched for `false`. Passing a specialization as a template argument should not *per se* cause that specialization to be instantiated. – Columbo Dec 01 '14 at 16:27
  • @Yakk VS2012 is now happy with it (minor typo: `Ts` should be `T`); I'll try it on some others before accepting though! – Ben Hymers Dec 01 '14 at 16:30
  • VS2012 doesn't like the simpler version in your latest edit; `error C2893: Failed to specialize function template`. – Ben Hymers Dec 01 '14 at 16:38
  • @colu knew there was a reason I shyed away from that. – Yakk - Adam Nevraumont Dec 01 '14 at 16:44
0

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);
}
Ben Hymers
  • 25,586
  • 16
  • 59
  • 84
  • Replace `static inner` with `operator() const` to be more idiomatic. Then `to_integral_helper()(e)` does the same thing as `to_integral_helper::inner(e)`, and you have a stateless functor that can be used elsewhere. – Yakk - Adam Nevraumont Dec 01 '14 at 16:31