Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why SFINAE doesn't work in right side in default function arguments?

Tags:

c++

sfinae

I have this code:

struct My
{
   typedef int foo;
};

struct My2
{
};


template <typename T>
void Bar(const T&, int z = typename T::foo())
{
    std::cout << "My" << std::endl; 
}


void Bar(...)
{
    std::cout << "..." << std::endl; 
}

int main() 
{
    My my;
    Bar(my); // OK
    My2 my2;
    Bar(my2); // Compile error: no type named ‘foo’ in ‘struct My2’
    return 0;
}

I suppose, that if some class T doesn't have typedef foo inside, compiler should exclude first overload and choose overload with ellipsis. But I check this code on MSVC, gcc and clang and I get compile error on those compilers. Why SFINAE doesn't work in this case?

like image 472
maxim.yurchuk Avatar asked Jul 23 '14 11:07

maxim.yurchuk


People also ask

Which are the rules for default arguments?

Characteristics for defining the default argumentsThe values passed in the default arguments are not constant. These values can be overwritten if the value is passed to the function. If not, the previously declared value retains. During the calling of function, the values are copied from left to right.

What are the rules of default argument in C++?

Default Arguments in C++ A default argument is a value provided in a function declaration that is automatically assigned by the compiler if the calling function doesn't provide a value for the argument. In case any value is passed, the default value is overridden.

Can we give overloaded functions default arguments?

No you cannot overload functions on basis of value of the argument being passed, So overloading on the basis of value of default argument is not allowed either.

What can be used as a default function argument?

Default arguments are only allowed in the parameter lists of function declarations and lambda-expressions, (since C++11) and are not allowed in the declarations of pointers to functions, references to functions, or in typedef declarations.


1 Answers

The type of z is not subject to template substitution, it is always int. This means there is no opportunity for SFINAE, and you instead get a compiler error when attempting to resolve T::foo for the default value. Default arguments do not participate in overload resolution, instead being instantiated only when missing from the function call. Section 14.7.1 (paragraphs 13/14) of the standard describes this behaviour, but does not give justification for the lack of SFINAE here.

SFINAE can be allowed to happen by making the type of z a template parameter, as below:

(live example: http://ideone.com/JynMye)

#include <iostream>

struct My
{
   typedef int foo;
};

struct My2
{
};

template<typename T, typename I=typename T::foo> void Bar(const T&, I z = I())
{
    std::cout << "My\n";
}

void Bar(...)
{
    std::cout << "...\n";
}

int main() 
{
    My my;
    Bar(my); // OK
    My2 my2;
    Bar(my2); // Also OK
    return 0;
}

This will use the "My" version for the first call, and the "..." version for the second call. The output is

My
...

However, if void Bar(...) was a template, for whatever reason, the "My" version will never get a chance:

(live example: http://ideone.com/xBQiIh)

#include <iostream>

struct My
{
   typedef int foo;
};

struct My2
{
};

template<typename T, typename I=typename T::foo> void Bar(const T&, I z = I())
{
    std::cout << "My\n";
}

template<typename T> void Bar(T&)
{
    std::cout << "...\n";
}

int main() 
{
    My my;
    Bar(my); // OK
    My2 my2;
    Bar(my2); // Also OK
    return 0;
}

Here, the "..." version is called in both cases. The output is:

...
...

One solution is to use class template (partial) specialisation; provide the "..." version as the base, with the type of the second parameter defaulted to int, and the "My" version as a specialisation where the second parameter is typename T::foo. In conjunction with a plain template function to deduce T and dispatch to the appropriate class' member function, this produces the desired effect:

(live example: http://ideone.com/FanLPc)

#include <iostream>

struct My
{
   typedef int foo;
};

struct My2
{
};

template<typename T, typename I=int> struct call_traits {
    static void Bar(...)
    {
        std::cout << "...\n";
    }
};

template<typename T> struct call_traits<T, typename T::foo> {
    static void Bar(const T&, int z=typename T::foo())
    {
        std::cout << "My\n";
    }
};

template<typename T> void Bar(const T& t)
{
    call_traits<T>::Bar(t);
}

int main() 
{
    My my;
    Bar(my); // OK
    My2 my2;
    Bar(my2); // Still OK
    return 0;
}

Here, the output is:

My
...
like image 100
Andrew Avatar answered Sep 19 '22 00:09

Andrew