8

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 , why isn't Visual Studio 2015 supporting this? Is it a bug?

Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • 1
    `constexpr` functions were in fact introduced in C++11. – Kerrek SB Jan 11 '17 at 14:37
  • https://msdn.microsoft.com/en-us/library/hh567368.aspx They don't support extended constexpr FWIW. – Hatted Rooster Jan 11 '17 at 14:38
  • VS doesn't have complete Expression SFINAE support yet – bolov Jan 11 '17 at 14:42
  • @KerrekSB Thanks I've updated. That just puts Visual Studio 2015 3 more years out of date. – Jonathan Mee Jan 11 '17 at 14:43
  • Try `template ()>> void test() { cout << "true\n"; }`? – Yakk - Adam Nevraumont Jan 11 '17 at 14:44
  • @GillBates Is this "extended `constexpr`" functionality? As has been pointed out this should have worked since C++11. – Jonathan Mee Jan 11 '17 at 14:44
  • @bolov I'm not sure what that means but it seems like it might be the problem. – Jonathan Mee Jan 11 '17 at 14:45
  • @JonathanMee The main flaw in MSVC's SFINAE is not extended `constexpr`, but what they call "expression SFINAE". The biggest part that it means is that `decltype` doesn't really work with SFINAE, but what exactly it covers is not something I've ever been able to work out. Maybe it is causing a problem here. But the error message makes me think that it is seeing two templates it somehow considers identical, which may be an unrelated problem. Each version of MSVC states they have solved some "expression SFINAE" problems. :/ – Yakk - Adam Nevraumont Jan 11 '17 at 14:46
  • @Yakk That didn't work for me either, still gives error C2995. You can test for yourself here: http://rextester.com/QMEU55701 – Jonathan Mee Jan 11 '17 at 14:51
  • @JonathanMee it simply means that other than very simple expressions for SFINAE you can't be sure SFINAE will work on VS. – bolov Jan 11 '17 at 14:54
  • @JonathanMee The solution suggested by `Yakk` works: http://rextester.com/XLEV56307 – Simon Kraemer Jan 11 '17 at 14:58
  • @SimonKraemer Actually it doesn't: http://rextester.com/live/OFNM72056 But strangely when you add in the `enable_if` on the first `test`'s return it does work o.O – Jonathan Mee Jan 11 '17 at 15:02
  • @JonathanMee .... Oh I missed that. – Simon Kraemer Jan 11 '17 at 15:04
  • @SimonKraemer That just makes me more confused at what's going on here. – Jonathan Mee Jan 11 '17 at 15:05
  • @bolov Well... this MCVE is very simple compared to [what I'm actually trying to do](http://stackoverflow.com/a/41592619/2642059). Does that count? – Jonathan Mee Jan 11 '17 at 15:22
  • 1
    @JonathanMee I can offer you a workaround by using a template struct as proxy. http://rextester.com/TGMB94634 – Simon Kraemer Jan 11 '17 at 15:30
  • @JonathanMee yes, it means that your real example has even less chance to work on VS. Take it as it is: VS doesn't yet have support this feature. They are working on it, as Yakk said, they incrementally added support with each version. You have a few options: a) wait until they implement it in a future version, b) switch to a different compiler (this might not be a solution) c) search for a workaround (you should focus on finding a workaround) – bolov Jan 11 '17 at 15:30
  • 5
    https://blogs.msdn.microsoft.com/vcblog/2016/06/07/expression-sfinae-improvements-in-vs-2015-update-3/ – T.C. Jan 11 '17 at 15:30
  • 1
    @JonathanMee The workaround applied to your example: http://rextester.com/ULDFM22040 – Simon Kraemer Jan 11 '17 at 15:38
  • @SimonKraemer Yup, I was just reading through all the bugs... Yikes. If you care to post as an answer I will accept shortly. – Jonathan Mee Jan 11 '17 at 15:41
  • @T.C. Thanks that had the workaround for me, albeit a somewhat ugly one. – Jonathan Mee Jan 11 '17 at 15:42
  • @GillBates It looks like they have partial support for extended constexpr, but they're not all the way there yet. [This](http://ideone.com/sCPYi0) works on [the online MSVC compiler](http://webcompiler.cloudapp.net/), but I believe Community edition still has trouble with it (not 100% sure of this, though). – Justin Time - Reinstate Monica Jan 11 '17 at 17:49
  • @JustinTime Hmmm... I am using the community version... Though my current problem certainly extends to all versions. I thought that the compiler was consistent across all the versions anyway? – Jonathan Mee Jan 11 '17 at 18:03
  • @JonathanMee I believe all of the installable versions use the same compiler, but I'm not sure if the online compiler does, too; the online one at cloudapp has a different build number than the VS 2015 Update 3 one (19.10.24903.0 for Cloudapp, 14.0.25420.10 for Update 3), but I'm not sure if that indicates anything more than that it's an online build. I _think_ the online one is updated more frequently than major updates are pushed to the installable versions (especially considering it was updated a bit less than a week ago), but that's honestly just a guess. – Justin Time - Reinstate Monica Jan 11 '17 at 18:13

4 Answers4

6

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

Community
  • 1
  • 1
Simon Kraemer
  • 5,700
  • 1
  • 19
  • 49
  • Just as a comment to my answer: In most cases you won't even need the constexpr function and can go with the struct only. In the example above you could easily replace `static const bool value = condition();` with `static const bool value = sizeof(T) > 1;` and get rid of the constexpr function. I would say that it depends on the use case and personal preference which way to here. – Simon Kraemer Jan 11 '17 at 17:50
  • I would never, ever trust a MSVC SFINAE case where there is `decltype` as part of the decision to fail or not; I've seen far to many bugs, where it "cache"s the wrong result and gets completely insane behavior. Maybe your "go through a template type" solution avoids this, but I'd want to see extensive testing before I trusted it. The "incomplete support" basically means "it goes wrong in unpredictable ways rather than failing with an error". It is horrid. – Yakk - Adam Nevraumont Jan 11 '17 at 18:42
  • @Yakk Good to know. In this case `divtype` should rather provide 3 specializations for the 3 overloads of `std::div` – Simon Kraemer Jan 11 '17 at 19:13
3

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Can you help me understand what `counter_type` is doing in the template parameters? It seems completely unrelated to the other template arguments. Maybe that's the point? – Jonathan Mee Jan 11 '17 at 17:39
  • @JonathanMee Guarantees they have different template signatures – Yakk - Adam Nevraumont Jan 11 '17 at 17:42
  • As far as I understand it: `counter_type<0>` is different from `counter_type<1>` making the two template functions "really" different from another. As it is variadic (see the ellipsis) you don't have to pass anything at this position. It just helps the compiler to distinguish between the 2 functions. – Simon Kraemer Jan 11 '17 at 17:43
  • @SimonKraemer Yeah that's what I got too. Seems like a pretty high hoop to jump through when the SFINAE should already work :( – Jonathan Mee Jan 11 '17 at 17:46
2

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.

davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • Can you help me understand this? So if the `enable_if` succeeds we're compiling `template void test() { cout << "true\n"; }` that assignment in the template parameters doesn't make any sense to me. – Jonathan Mee Jan 11 '17 at 17:28
  • @JonathanMee: see [here](http://stackoverflow.com/questions/36499008/sfinae-works-differently-in-cases-of-type-and-non-type-template-parameters). Funnily, it's also Yakk who gave the answer :-) – davidhigh Jan 11 '17 at 17:35
  • 1
    Does this really work? Rextester doesn't like it: http://rextester.com/ZCLIE12472 – Simon Kraemer Jan 11 '17 at 17:36
  • @SimonKraemer: thanks for the comment. [coliru does](http://coliru.stacked-crooked.com/a/3d2099231fad37aa) (and it also did before C++11 afai remember), moreover it's a basic trick ... not sure why rextester doesn't. – davidhigh Jan 11 '17 at 17:41
  • @Yakk: seems so. (anyways I think I remember it worked for me in MSVC2015, but maybe I'm wrong) – davidhigh Jan 11 '17 at 17:44
  • Tried it locally and it doesn't work here either. Same error. – Simon Kraemer Jan 11 '17 at 17:45
  • Sorry, my memory was refering to a slightly different case, namely where `condition()` is directly replaced by the condition itself, i.e. here `(sizeof(T)>1)`. Then it works in MSVC 2015. Otherwise, Simon's answer applies. – davidhigh Jan 11 '17 at 17:57
1

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>(); }