Search code examples
c++gccvisual-studio-2015clangfunction-templates

Template Function Selection Based on Nested Type


The following code works correctly on VS2015:

struct Foo
{
   using Bar = int;
   auto operator()() { return "Foo!";  }
};

template <typename Callable, typename CodeType> // <<< CodeType is a template param
void funky(CodeType code, Callable func)
{
   cout << "Generic: " << code << ", " << func() << endl;
}

template <typename HasBar>
void funky(typename HasBar::Bar code, HasBar func) // <<< The code type is a nested type
{
   cout << "Has Bar: " << code << ", " << func() << endl;
}

int main()
{
   Foo foo;
   funky(3, []() { return "Lambda!"; });
   funky(3, foo);
   return 0;
}

Printing:

Generic: 3, Lambda!
Has Bar: 3, Foo!

However, it does not compile on gcc/clang, complaining:

 In function 'int main()':
27:16: error: call of overloaded 'funky(int, Foo&)' is ambiguous
27:16: note: candidates are:
12:6: note: void funky(CodeType, Callable) [with Callable = Foo; CodeType = int]
18:6: note: void funky(typename HasBar::Bar, HasBar) [with HasBar = Foo; typename HasBar::Bar = int]

The ambiguity is resolved correctly by VS2015 (which does not mean it is the conforming thing to do).

How can I get this to compile and run correctly on Clang/gcc?
I thought of using std::enable_if but couldn't get it to do what I want (I most likely used it incorrectly). If this is the way to go, how should it be used to solve this ambiguity?

UPDATE:
Adding typename HasBar::Bar to the template params gets gcc/Clang to build and run the code correctly:

template <typename HasBar, typename HasBar::Bar>
void funky(typename HasBar::Bar code, HasBar func) // <<< The code type is a nested type
{
   cout << "Has Bar: " << code << ", " << func() << endl;
}

This seems to tell the compiler that there is a second, non-type template parameter value (unused in the function code) that is of type typename HasBar::Bar. If typename HasBar::Bar does not exist, SFINAE will remove this function from the overload set and the generic form will be chosen.

However, when it does exist, I do not know why this function will take precedence over the first. I guess because it is more specialized - although the specialization isn't used in the code itself. However, in this case, it was already more specialized even before the new param!

However, in this case VS2015 always chooses the generic form giving the wrong answer!

Is there some syntax (and/or workaround) that will work in all cases?


Solution

  • I would do it with SFINAE(https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error) trick in a way like this:

    #include <iostream>
    #include <type_traits>
    
    using namespace std;
    
    struct Foo
    {
       using Bar = int;
       auto operator()() { return "Foo!";  }
    };
    
    template< typename ... Ts >
    using void_t = void;
    
    template< typename T, typename = void >
    struct has_type_Bar : false_type{};
    
    template< typename T >
    struct has_type_Bar< T, void_t<typename T::Bar> > : true_type {};
    
    template <typename Callable, typename CodeType>
    void funky_impl(CodeType code, Callable func, false_type)
    {
       cout << "Generic: " << code << ", " << func() << endl;
    }
    
    template <typename Callable, typename CodeType>
    void funky_impl(CodeType code, Callable func, true_type)
    {
       cout << "Has Bar: " << code << ", " << func() << endl;
    }
    
    template <typename Callable, typename CodeType>
    void funky(CodeType code, Callable func)
    {
        return funky_impl( code, func, has_type_Bar<CodeType>{} );
    }
    
    int main()
    {
       Foo foo;
       funky(3, []() { return "Lambda!"; });
       funky(3, foo);
       return 0;
    }
    

    For VS2015, I guess it has a bug with two-phase name lookup(What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?).