简体   繁体   中英

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

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?

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

The type z is an int , is not being deduced by the compiler, no room for SFINAE to take place. The value being used to initialise z is based on the default of T::foo , which doesn't exist; hence the error.

If the type for z is elevated to the template itself, substitution can now fail, and SFINAE kicks in.

#include <iostream>

struct My
{
   typedef int foo;
};

struct My2
{
};

template <typename T, typename I = typename T::foo>
void Bar(const T&, I z = I())
{
    (void)z; // silence any warnings on unused
    std::cout << "My" << std::endl; 
}

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

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

Live sample

In order for a function template to be part of the overloaded list of candidate functions, the template argument deduction must succeed. If it fails, then the candidate is removed from the list. Hence, if no deduction failure occurs, it is added to the candidate list (but this does not preclude further errors if it is finally selected).

14.8.3/1 Overload resolution

A function template can be overloaded either by (non-template) functions of its name or by (other) function templates of the same name. When a call to that name is written (explicitly, or implicitly using the operator notation), template argument deduction (14.8.2) and checking of any explicit template arguments (14.3) are performed for each function template to find the template argument values (if any) that can be used with that function template to instantiate a function template specialization that can be invoked with the call arguments. For each function template, if the argument deduction and checking succeeds, the template arguments (deduced and/or explicit) are used to synthesize the declaration of a single function template specialization which is added to the candidate functions set to be used in overload resolution. If, for a given function template, argument deduction fails, no such function is added to the set of candidate functions for that template. The complete set of candidate functions includes all the synthesized declarations and all of the non-template overloaded functions of the same name. The synthesized declarations are treated like any other functions in the remainder of overload resolution, except as explicitly noted in 13.3.3.

Template argument deduction is performed on the function type and its template arguments themselves.

14.8.2/8 Template argument deduction

If a substitution results in an invalid type or expression, type deduction fails. An invalid type or expression is one that would be ill-formed, with a diagnostic required, if written using the substituted arguments. [ Note: If no diagnostic is required, the program is still ill-formed. Access checking is done as part of the substitution process. —end note ] Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure.

From the OP, the function Bar<T> is added to the candidate list since it can be deduced what the type for T is. It is instantiated and the default arguments are checked, and hence it fails.

14.7.1/13 Implicit instantiation

If a function template f is called in a way that requires a default argument to be used, the dependent names are looked up, the semantics constraints are checked, and the instantiation of any template used in the default argument is done as if the default argument had been an initializer used in a function template specialization with the same scope , the same template parameters and the same access as that of the function template f used at that point. This analysis is called default argument instantiation. The instantiated default argument is then used as the argument of f .

Quotes taken from draft n3797

One more C++03 compatible option for you. Because in answers above default argument was used in template function and it is not permitted in standard.

#include <iostream>

struct TypeWithFoo{
   typedef int Foo;
};

template<typename T, bool>
struct onFooAction;

template<typename T>
struct onFooAction<T, false>{
   void operator ()(const T &t){
        std::cout << "No foo :(\n";
   }
};

template<typename T>
struct onFooAction<T, true>{
   void operator ()(const T &t){
      std::cout << "Foo =)\n";
   }
};

template<typename T>
struct hasFoo{
   typedef char yes[1];
   typedef char no[2];

   template<typename C>
   static yes& testForFoo(typename C::Foo*);

   template<typename>
   static no& testForFoo(...);

   static const bool value = sizeof(testForFoo<T>(0)) == sizeof(yes);
};

template<typename T>
void bar(const T &t){
   onFooAction<T, hasFoo<T>::value>()(t);
}

int main(){
  bar(10);
  bar(TypeWithFoo());
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM