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

Template Function Selection Based on Nested Type

以下代码在 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;
}

正在打印:

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

然而,it does not compile 在 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]

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

如何在 Clang/gcc 上正确编译和 运行?
我想过使用 std::enable_if 但无法让它做我想做的事(我很可能使用不正确)。如果这是要走的路,应该如何解决这个歧义?

更新:
添加 typename HasBar::Bar 到模板参数得到 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;
}

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

但是,当它确实存在时,我不知道为什么这个函数会优先于第一个。我猜是因为它更专业——尽管代码本身没有使用专业化。 然而,在这种情况下,它甚至在新参数出现之前就已经更加专业化了!

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

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

13 分钟后...回答我自己。 [不] 已解决!

typename HasBar::Bar 添加到模板参数 did the trick:

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;
}

这似乎告诉编译器还有第二个非类型模板参数值(在函数代码中未使用)类型为 typename HasBar::Bar。如果 typename HasBar::Bar 不存在,SFINAE 将从重载集中删除该函数,并选择泛型形式。

但是,当它存在时,我不知道为什么这个函数会优先于第一个。我猜是因为它更专业化——尽管代码本身没有使用专业化。
然而,在这种情况下,它甚至在新参数出现之前就已经更加专业化了!

我会用 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;
}

对于 VS2015,我猜它有一个两阶段名称查找的错误(What exactly is "broken" with Microsoft Visual C++'s two-phase template instantiation?)。

回答关于为什么的问题...

理解这一点的一种简单方法是手动执行编译器将执行的操作。

鉴于:

// first form
template <typename Callable, typename CodeType>
void funky(CodeType code, Callable func);

// second form
template <typename HasBar, typename HasBar::Bar>
void funky(typename HasBar::Bar code, HasBar func);

代入 lambda, int:

第一种形式: template<lambda, int> void funky(int, lambda) - 很好,需要两次替换

第二种形式: template<lambda, lambda::Bar> <-- 此处出现 SFNAE 错误,因为 lambda 没有 Bar 从属类型。

因此选择第一种形式(唯一合法的形式)

替换 HasBar,HasBar::Bar:

第一种形式:template<HasBar, HasBar::Bar> void funky(HasBar::Bar, HasBar) - 很好,需要两次替换

第二种形式: template<HasBar, HasBar::Bar> <-- 很好,需要零替换。

两者都有效,但第二种形式需要较少的模板替换。因此它更专业,更匹配。

回答问题:

好的...让我们考虑一种在编译器中实现模板匹配算法的方法...

在这个调用点 funky(3, []() { return "Lambda!"; });:

compute the "ambiguity" of each template...
let's measure ambiguity as the total number of combinations of 
known types that would make a match.
This would be a set of possible substitutions. The set is limited
to the product of the total number of types known for each template
argument... for now let's just consider the ones mentioned in the program and ignore all others (like float, ostream etc.)

for the first form: template<Callable, CodeType>
Callable can be: Foo, int, some_lambda
When Callable is Foo, CodeType can be: Foo, int, some_lambda
When Callable is int, CodeType can be: Foo, int, some_lambda
... etc
for a total ambiguity score of 3 * 3 = 9

now the second form: template<HasBar, HasBar::Bar>
HasBar can be: Foo, int, some_lambda
When HasBar is Foo, HasBar::Bar can only be: Foo::Bar, because Foo::int and Foo::some_lambda are not legal syntax = 1
When HasBar is int, HasBar::Bar can't be anything because int::something is illegal, so score = 0
When HasBar is some_lambda, HasBar::Bar can't be anything because lambda::something is illegal, so score = 0
... for a total ambiguity score of 1 + 0 + 0 = 1

... ok, now we know how ambiguous each template is, let's see if the arguments can be substituted into one of the options for each...
... if the arguments can't be legally substituted, dump the template as an option ...
... if we have any templates left in our 'legal' set, choose the one with the least 'ambiguity'
... if there is more than one legal templates with the minimum ambiguity, error (as you saw in the beginning)
... otherwise, we have a match - use this template function.