问题
So I've boiled this down to the minimal, complete, verifiable example and it seems that Visual Studio 2015 just won't allow me to use a templatized, constexpr
function in an enable_if
.
For example:
template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }
Gives me the error:
error C2995:
enable_if<_Test,T>::type test(void)
: function template has already been defined
When I try to use it in substitution failure is not an error compilation like this:
template <typename T>
enable_if_t<condition<T>()> test() { cout << "true\n"; }
template <typename T>
enable_if_t<!condition<T>()> test() { cout << "false\n"; }
This works fine in gcc: http://ideone.com/m9LDdS
And it works fine in Visual Studio 2015 if I remove the templatization of condition
. I believe that constexpr
functions were introduced in c++11, why isn't Visual Studio 2015 supporting this? Is it a bug?
回答1:
The problem seems to be that MSVC14/VS2015 is not capable of correctly resolving SFINAE expressions in combination with return values of constexpr functions as template parameters.
As a workaround you can assign the return value of your constexpr to a 'static const' member of a struct and use this member as template parameter.
#include <type_traits>
#include <iostream>
using std::enable_if_t;
using std::cout;
template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }
template <typename T>
struct condition_ { static const bool value = condition<T>();};
template <typename T>
enable_if_t<condition_<T>::value> test() { cout << "true\n"; }
template <typename T>
enable_if_t<!condition_<T>::value> test() { cout << "false\n"; }
int main() {
test<int>();
test<bool>();
return 0;
}
http://rextester.com/VVNHB62598
You also mentioned in the comments that your actual problem appeared in another case than your MCVE (How can I Initialize a div_t Object?)
For this case the workaround might look like this:
#include <type_traits>
#include <cstdlib>
#include <utility>
template <typename T>
using divtype = decltype(std::div(std::declval<T>(), std::declval<T>()));
template <typename T>
struct condition
{
static const bool value = divtype<T>{ 1, 0 }.quot != 0;
};
template <typename T>
std::enable_if_t<condition<T>::value, divtype<T>> make_div(const T quot, const T rem) { return{ quot, rem }; }
template <typename T>
std::enable_if_t<!condition<T>::value, divtype<T>> make_div(const T quot, const T rem) { return{ rem, quot }; }
int main() {
make_div<int>(1, 2);
return 0;
}
http://rextester.com/ULDFM22040
According to this Visual Studio C++ Team blog entry VS2015 does not have (complete) support for Expression SFINAE yet.
[1] We’re planning to start implementing Expression SFINAE in the compiler immediately after 2015 RTM, and we’re planning to deliver it in an Update to 2015, supported for production use. (But not necessarily 2015 Update 1. It might take longer.)
回答2:
The problem is that you end up with two different template test
of the form:
template<class>void test()
and the compiler complains. This may be related to expression SFINAE failure, where it doesn't evaluate the expression of condition<T>()
"early enough", or fails in another way.
Here is a workaround:
template<std::size_t>
struct counter{ enum type{}; };
template<std::size_t N>
using counter_type=typename counter<N>::type;
template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }
template <class T,counter_type<0>...,class=std::enable_if_t<condition<T>()>>
void test() { std::cout << "true\n"; }
template <class T,counter_type<1>...,class=std::enable_if_t<!condition<T>()>>
void test() { std::cout << "false\n"; }
Now the template signatures of the two different test
differ, and the expression SFINAE evaluation of condition<T>()
not working quite right doesn't cause a problem.
回答3:
Another option to @Yakk's answer:
template <class T, std::enable_if_t<(sizeof(T) > 1)>* = nullptr>
void test() { std::cout << "true\n"; }
template <class T, std::enable_if_t<!(sizeof(T) > 1)>* = nullptr>
void test() { std::cout << "false\n"; }
EDIT: just saw that this is basically the solution to which TC pointed to in the comments.
EDIT2: corrected code to compile in MSVC2015, see comments.
回答4:
This is a known MSVC issue, and was mentioned in one of their blog posts. What's going on is that the compiler can't recognise that SFINAE makes the second version of test()
different than the first, and it needs a little hint; a simple dummy parameter will suffice, allowing it to differentiate the two versions.
#include <type_traits>
#include <iostream>
using std::enable_if_t; using std::cout;
template <typename T>
constexpr bool condition() { return sizeof(T) > 1; }
#ifdef _MSC_VER
#define MSVC_DUMMY int /*msvc_dummy*/ = 0
#else // _MSC_VER
#define MSVC_DUMMY
#endif // _MSC_VER
template <typename T>
enable_if_t<condition<T>()> test() { cout << "true\n"; }
template <typename T>
enable_if_t<!condition<T>()> test(MSVC_DUMMY) { cout << "false\n"; }
int main() {
test<char>();
test<int>();
}
This works on MSVC, with only minimal modifications to the code. It's also easy to remove once they eventually get it working without the hint.
If a consistent interface is desired, this can be hidden behind a helper function.
template <typename T>
enable_if_t<condition<T>()> test_() { cout << "true\n"; }
template <typename T>
enable_if_t<!condition<T>()> test_(MSVC_DUMMY) { cout << "false\n"; }
template <typename T>
auto test() { return test_<T>(); }
来源:https://stackoverflow.com/questions/41593649/why-wont-visual-studio-let-me-use-a-templatized-constexpr-function-in-enable-i