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

时间:2016-05-17 12:41:36

标签: c++ gcc visual-studio-2015 clang function-templates

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

然而,gcc / clang上的it does not compile抱怨:

 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添加到模板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;
}

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

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

然而 ,在这种情况下,VS2015总是选择通用表单给出错误答案!

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

3 个答案:

答案 0 :(得分:4)

13分钟后......回答自己。 [NOT] 解决了!

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将从重载集中删除此函数,并选择通用表单。

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

答案 1 :(得分:3)

回答有关原因的问题......

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

假设:

// 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>&lt; - SFNAE错误,因为lambda没有Bar从属类型。

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

代替HasBar,HasBar :: Bar:

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

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

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

回答问题:

ok ...让我们考虑一下我们可以在编译器中实现模板匹配算法的方法......

此致电话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.

答案 2 :(得分: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;
}

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