c++visual-studio-2015c++14portinggcc5

Porting from MSVC2015 Update 2 to GCC 5.3 - SFINAE Errors


I'm currently porting my library, but my dark template magic won't get compiled with GCC 5.3

This fragment works as expected when compiled with MSVC2015 Update 2

template<typename vreal = 
    std::enable_if<std::is_vreal<vreal>::value, 
                   floatType>::type>
inline vreal foo(vreal bar)
{
    return bar;  
}

template<typename vreal = 
    std::enable_if<std::is_vreal<vreal>::value, 
                   floatType>::type>
struct bar { vreal i; };

GCC complains about "vreal was not defined in current scope (WTF?)"

What I've tried so far:

Rewrite the upper template fragment to

template<typename vreal, 
         typename enable = typename std::enable_if<std::is_vreal<vreal>::value != 0>::type>

But this doesn't work either. It breaks much much later in the code and I assume that it's due the introduction of the additional template param.

Also, I don't understand why I had to introduce the comparison with 0. Without it gcc complained about missing 'type' on enabled_if.

So, the main question is: how to get the same SFINAE logic (compile ONLY if the argument is vreal) WITHOUT additional arguments.

I could rewrite that to return type SFINAE - but that would be a lot of work which I'd like to avoid (differentiating between functions, classes, structs, typedefs / usings...) even though it's wrapped in a macro.

 template<typename vreal>
 typename std::enable_if<is_vreal<vreal>, vreal>::type inline vreal .....

Solution

  • This is not valid C++:

    template<typename vreal = 
      std::enable_if<std::is_vreal<vreal>::value, 
                   floatType>::type>
    inline vreal foo(vreal bar)
    {
      return bar;  
    }
    

    for many reasons. It uses a symbol in std that isn't in std (is_vreal), which means your program is ill-formed. It uses a token floatType that is not defined (you did post a [MCVE] right?). It uses vreal before it is in scope, in the definition of vreal.

    I have no idea what it is supposed to mean, other than your seeming belief that it does SFINAE magic: it states that if vreal passes the is_vreal test, it should be the type floatType by default. But in order to reach this point, you had to have the type vreal already, so the default type doesn't seem to matter.

    Also, ::type is not a type in a dependent context:, so std::enable_if<std::is_vreal<vreal>::value, floatType>::type should complain that you are using a non-type named ::type in a context where a type is expected. You need to do typenam estd::enable_if<std::is_vreal<vreal>::value, floatType>::type.

    You seem to also state that you are using macros to generate code. That was a poor decision.

    As far as I can tell, simply removing the enable_if clause completely will solve your problem for most cases.

    The exception is for functions, because there can be overloads, you can introduce a SFINAE helper.

    Ditch the macro completely. Class and function templates work significantly differently anyhow.

    For classes/structs:

    template<class vreal>
    struct bar { vreal i; };
    

    because there really isn't another alternative -- there isn't the concept of overloading with structures/classes like there is with functions.

    For functions, we want a SFINAE test so we can overload:

    template<class T, class R=T>
    using vreal_test = typename
      std::enable_if<std::is_vreal<T>::value, R>::type
    
    template<class vreal>
    inline vreal_test<vreal> foo(vreal bar)
    {
      return bar;  
    }
    

    if the function returns a differnet type, do

    template<class vreal>
    inline vreal_test<vreal,void> foo2(vreal bar)
    {
      return;
    }
    

    You can do this as you sweep to remove your macro.