Scott Langham
Scott Langham

Reputation: 60381

Why can't this enable_if function template be specialized in VS2017?

The following compiled with VS2015, but fails in VS2017 with the below errors. Was the code doing something non standard that has been fixed in VS2017, or should VS2017 compile it?

#include "stdafx.h"
#include <type_traits>

template <typename E>
constexpr auto ToUnderlying(E e)
{
    return static_cast<std::underlying_type_t<E>>(e);
}

template<typename T>
bool constexpr IsFlags(T) { return false; }

template<typename E>
std::enable_if_t<IsFlags(E{}), std::underlying_type_t<E>> operator | (E lhs, E rhs)
{
    return ToUnderlying(lhs) | ToUnderlying(rhs);
}

enum class PlantFlags { green = 1, edible = 2, aromatic = 4, frostTolerant = 8, thirsty = 16, growsInSand = 32 };

bool constexpr IsFlags(PlantFlags) { return true; }

int main()
{
    auto ored = PlantFlags::green | PlantFlags::frostTolerant;

    return 0;
}

Errors are:

c:\main.cpp(24): error C2893: Failed to specialize function template 'enable_if<false,_Ty>::type
operator |(E,E)'
        with
        [
            _Ty=underlying_type<_Ty>::type
        ] 
c:\main.cpp(24): note: With the following template arguments:
c:\main.cpp(24): note: 'E=PlantFlags'
c:\main.cpp(24): error C2676: binary '|': 'PlantFlags' does not define this operator or a conversion to a type acceptable to the predefined operator

Upvotes: 8

Views: 2009

Answers (2)

Justin
Justin

Reputation: 25337

This is definitely a bug in Visual Studio. It compiles on GCC and Clang. It seems to be related with constexpr functions evaluated as template parameters. As a temporary workaround, you can make a template variable:

template <typename T>
bool constexpr is_flags_v = IsFlags(T{});

template<typename E>
std::enable_if_t<is_flags_v<E>, std::underlying_type_t<E>> operator | (E lhs, E rhs)
{
    return ToUnderlying(lhs) | ToUnderlying(rhs);
}

On Godbolt

Upvotes: 4

Jodocus
Jodocus

Reputation: 7601

This might be a bug in Visual Studio. A possible workaround could be using template specialization instead of overloading:

template <typename T>
struct is_flags { constexpr static bool value = false; };

template <>
struct is_flags<PlantFlags> { constexpr static bool value = true; };

template<typename E> std::enable_if_t<is_flags<E>::value, std::underlying_type_t<E >> operator | (E lhs, E rhs)
{
    return ToUnderlying(lhs) | ToUnderlying(rhs);
}

Upvotes: 4

Related Questions