10

My SFINAE code using std::enable_if compiles in GCC & Clang, but not in MSVC 2013.

The code (also available on cpp.sh) is

#include <iostream>
#include <type_traits>

template <typename T, typename ... AdditionalInputs>
typename std::enable_if<sizeof...(AdditionalInputs) == 0, void>::type
CallDoDataProcessing(T var) {
    std::cout << sizeof...(AdditionalInputs) << " additional inputs" << std::endl;
}

template <typename T, typename ... AdditionalInputs>
typename std::enable_if<sizeof...(AdditionalInputs) == 1, void>::type
CallDoDataProcessing(T var) {
    std::cout << sizeof...(AdditionalInputs) << " additional inputs" << std::endl;
}

int main() {
    CallDoDataProcessing<int>(3);
    CallDoDataProcessing<int, int>(3);
    return 0;
}

In GCC/Clang, this works perfectly, in MSVC though, I get:

Error   1   error C2039: 'type' : is not a member of 'std::enable_if<false,void>'   c:\Users\mrussell\documents\visual studio 2013\Projects\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp 5   1   ConsoleApplication1

The compiled and run output should be:

0 additional inputs
1 additional inputs

I've seen some similar issues on SO, but none had a clear answer or were slightly tangental.

Reading the MSVC enable_if page, this should work...

How can I use SFINAE in MSVC2013?

UPDATE

Just as a note, this does work in the positive case. For instance, if I comment out the first function, and the call to it, then the rest compiles. i.e. the enable_if<true, void> on CallDoDataProcessing does have a type member.

However, commenting out the second function and call to it (so, leaving the version where sizeof...(AdditionalInputs) == 0 does not work though. Same error.

This suggests that the sizeof...(AdditionalInputs) == 0 call is not being matched, but I can't figure out why it wouldn't be.

Matt
  • 1,928
  • 24
  • 44
  • Did you `#include `? – AndyG Sep 19 '16 at 17:52
  • @AndyG Never thought of that, I just tried it though and it didn't seem to make a difference. I'll modify the code above to show that included. – Matt Sep 19 '16 at 17:54
  • 1
    My first guess is that you need to update MSVC – Tavian Barnes Sep 19 '16 at 18:05
  • 2
    @Matt: Yeah this seems like a bug with SFINAE. I can reproduce it. It appears fixed in VS 2015. – AndyG Sep 19 '16 at 18:06
  • I was worried about that... I'm told that we'll be updating to VS 2015 within a year, but nothing I can rely on right now. – Matt Sep 19 '16 at 18:13
  • Just a note, when I change the first template to `template typename std::enable_if::value, void>::type`, then I can match both of them, but this isn't extendable to adding a third or more (e.g. `sizeof...(AdditionalInputs) == 2`, `... == 3`, ...), in that case I just get this error again. That said, I think for now I can use that hack and just add a comment to update VS. – Matt Sep 19 '16 at 18:15
  • 1
    MSVC bug aside, the first overload is ill-formed with no diagnostic required, because every valid specialization of it requires an empty parameter pack. – T.C. Sep 19 '16 at 22:07
  • Is that bad though? This is my first time using parameter packs. I could just not use a parameter pack on the top overload, but I thought it might be more clear to leave them in. – Matt Sep 19 '16 at 22:51
  • This is still a problem in 2018. I encountered that error in MSVC 2017(v15.8.7) when compiling very similar code. – Koby Duck Nov 16 '18 at 22:44

1 Answers1

9

Try tag dispatching.

template<std::size_t>
struct size {};

namespace details {
  template <typename T, typename ... AdditionalInputs>
  void CallDoDataProcessing(T var, size<0>) {
    std::cout << sizeof...(AdditionalInputs) << ", aka 0, additional inputs" << std::endl;
  }

  template <typename T, typename ... AdditionalInputs, std::size_t N>
  void CallDoDataProcessing(T var, size<N>) {
    std::cout << sizeof...(AdditionalInputs) << " additional inputs" << std::endl;
  }
}
template <typename T, typename ... AdditionalInputs>
void CallDoDataProcessing(T var) {
  details::CallDoDataProcessing<T, AdditionalInputs>( var, size<sizeof...(AdditionalInputs)>{} );
}

SFINAE is really badly supported by MSVC. Your code looks like valid SFINAE. The fact that MSVC fails to do the right thing is not surprising.

MSVC works much much better with tag dispatching in my experience, and I find it even results in easier to understand code and even error messages sometimes.

What it does not permit is noticing "no, you cannot do this" before the body of the calling function, to make the calling function also state "no, I cannot be done".

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Nice! At first I thought this would also limit me to `AdditionalInputs` being 0 or 1, but I wrote up an [example that works with N inputs](http://cpp.sh/82nwr) – Matt Sep 19 '16 at 18:59
  • 2
    @matt you fell back on SFINAE `enable_if` stuff there. Again, MSVC doesn't do SFINAE very well. It can break from completely unrelated changes to the code in surprising ways. If you want `N` to be `1` or `2`, just take `size<1>` or `size<2>`, don't take `N` and then SFINAE say `N` should be `1` or `2`. Then leave a `N` generic one that handle "everything above your specific cases" if you need it. (Of course, ideally the generic one would handle every case, but...) – Yakk - Adam Nevraumont Sep 19 '16 at 19:13
  • Okay, I see what you mean. I played around with it, but habitually was still depending too much on SFINAE. I think I finally implemented your advice in [this example](http://cpp.sh/2bsyd) – Matt Sep 19 '16 at 19:41