基于嵌套类型的模板函数选择

Adi*_*vit 5 c++ gcc clang function-templates visual-studio-2015

以下代码在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;
}
Run Code Online (Sandbox Code Playgroud)

印刷:

Generic: 3, Lambda!
Has Bar: 3, Foo!
Run Code Online (Sandbox Code Playgroud)

但是,它不能在gcc/clang上编译,抱怨:

 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]
Run Code Online (Sandbox Code Playgroud)

VS2015正确地解决了歧义(这并不意味着它是符合要求的事情).

如何在Clang/gcc上编译并正确运行?
我想过使用std::enable_if但是无法让它做我想做的事情(我很可能错误地使用它).如果这是要走的路,应该如何使用它来解决这种模糊性?

更新:
将typename HasBar :: Bar添加到模板params获取gcc/Clang来构建并正确运行代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

这似乎告诉编译器存在类型的第二个非类型模板参数值(在函数代码中未使用)typename HasBar::Bar.If typename HasBar::Bar如果不存在,SFINAE将从过载集中删除此函数,并选择通用形式.

但是,当它确实存在时,我不知道为什么这个函数优先于第一个函数.我想因为它更专业 - 虽然代码本身没有使用专门化.但是,在这种情况下,它甚至在新的参数之前就已经更加专业了!

但是,在这种情况下,VS2015总是选择通用形式给出错误的答案!

是否有一些语法(和/或解决方法)适用于所有情况?

Fen*_*ang 2

我会用 SFINAE( https://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error ) 技巧来做到这一点,如下所示:

#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;
}
Run Code Online (Sandbox Code Playgroud)

对于 VS2015,我猜它有一个两阶段名称查找的错误(Microsoft Visual C++ 的两阶段模板实例化究竟“损坏”了什么?)。