简体   繁体   中英

SFINAE with default argument works with msvc but not with gcc and clang

I've learnt SFINAE in c++. Then I wrote the following program that compiles(and executes) with msvc but not with gcc and clang. Live demo

#include <iostream>
#include <type_traits>

struct C1{
    enum{
        c1 = 1
    };
};

struct C2{
    enum{
        c2 = 5
    };
};

template<class C>
class entity{
    void func1(){std::cout << "func1 called";} 
    void func2(){std::cout << "func2 called ";}
public:
    template<typename T = C>void common(bool b = std::is_same<T, C1>::value && std::is_enum<decltype(T::c1)>::value)
    {
        func1();
    }
        
    template<typename T = C>void common(int, bool b = std::is_same<T, C2>::value && std::is_enum<decltype(T::c2)>::value)
    {
        func2();
    }
    template<typename... T>
    void common(T...)
    {
        std::cout <<" General version " << std::endl;
    } 

}; 
int main()
{

    entity<C2> e;
    e.common(); //works with msvc but not with gcc and clang
    
}

As we can see, the call e.common() works with msvc but rejected by gcc and clang. GCC says:

In member function 'void entity<C>::common(bool) [with T = C2; C = C2]':
<source>:21:102: error: 'c1' is not a member of 'C2'
   21 |     template<typename T = C>void common(bool b = std::is_same<T, C1>::value && std::is_enum<decltype(T::c1)>::value)
      |                                                                                                      ^
<source>:41:13: note:   when instantiating default argument for call to 'void entity<C>::common(bool) [with T = C2; C = C2]'
   41 |     e.common(); //works with msvc but not with gcc and clang
      |     ~~~~~~~~^~

I want to know what is the correct behavior according to the C++ standard.


Note that I am not looking for a workaround but instead which compiler has the correct behavior. This is for academic purposes only.

The key here is that default arguments are not instantiated as part of the instantiation of the function template (or member function of class template) that they belong to. See [temp.inst]/5

Unless a call is to a function template explicit specialization or to a member function of an explicitly specialized class template, a default argument for a function template or a member function of a class template is implicitly instantiated when the function is called in a context that requires the value of the default argument.

Since the default argument is not instantiated until the function is "called", and you need to perform overload resolution to determine which overload is actually called, it follows that default argument instantiation occurs after overload resolution. So what happens is that the compiler first determines that the first overload of common takes precedence over the third one due to the former being more specialized (I will not elaborate here, since I imagine that you already understand this, but you can add another question if you want more details about the standardese that governs this determination) and only then, the default argument is instantiated and found to be ill-formed. At that point, it's too late for SFINAE to save you.

For greater clarity, note that SFINAE is governed by [temp.deduct.general]/6 et seq :

At certain points in the template argument deduction process it is necessary to take a function type that makes use of template parameters and replace those template parameters with the corresponding template arguments. This is done at the beginning of template argument deduction when any explicitly specified template arguments are substituted into the function type, and again at the end of template argument deduction when any template arguments that were deduced or obtained from default arguments are substituted.

The substitution occurs in all types and expressions that are used in the function type and in template parameter declarations. The expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments but also general expressions (ie, non-constant expressions) inside sizeof , decltype , and other contexts that allow non-constant expressions. The substitution proceeds in lexical order and stops when a condition that causes deduction to fail is encountered. If substitution into different declarations of the same function template would cause template instantiations to occur in a different order or not at all, the program is ill-formed; no diagnostic required.

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 in the same context using the substituted arguments. Only invalid types and expressions in the immediate context of the function type, its template parameter types, and its explicit-specifier can result in a deduction failure.

A default argument is not part of the function type, so if the default argument would be ill-formed, this is not discovered during the substitution process for the function template (or member function of class template) that it belongs to. Substitution failures during that process would have resulted in the function being removed from the overload set.

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