Using SFINAE to select function based on whether a particular overload of a function exists
我一直在尝试根据是否存在重载operator<<(std::ostream&, const T&),在两个模板化函数之间进行选择。
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
std::stringstream ss;
ss << t;
return ss.str();
}
template <typename T, typename std::enable_if</* ? */, int>::type = 0>
std::string stringify(const T& t)
{
return"No overload of operator<<";
}
struct Foo { };
int main()
{
std::cout << stringify(11) << std::endl;
std::cout << stringify(Foo{}) << std::endl;
} |
这有可能吗?如果是这样,你将如何解决这个问题?
- 该问题已标记为与非C++11问题重复。但是C++的11个答案可以在这里找到答案:StasOfFuff.com /A/915494/178537。
不需要使用enable_if,当存在operator<<时,使用表达式sfinae来选择正确的重载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| namespace detail
{
template<typename T>
auto stringify(std::stringstream& ss, T const& t, bool)
-> decltype(ss << t, void(), std::string{})
{
ss << t;
return ss.str();
}
template<typename T>
auto stringify(std::stringstream&, T const&, int)
-> std::string
{
return"No overload of operator<<";
}
}
template <typename T>
std::string stringify(const T& t)
{
std::stringstream ss;
return detail::stringify(ss, t, true);
} |
现场演示
stringify函数模板简单地委托给detail::stringify函数模板之一。然后,如果表达式ss << t格式正确,则选择第一个表达式。未命名的bool参数用于消除两个detail::stringify实现之间的歧义。由于主stringify函数通过true作为detail::stringify的参数,因此当operator<<过载时,第一个函数将是更好的匹配。否则将选择第二个。
第一个stringify模板的尾随返回类型中的表达式decltype(ss << t, void(), std::string{})可能需要更详细的解释。这里我们有一个单独的表达式,由3个子表达式组成,用逗号运算符分隔。
第一个是ss << t,它决定函数模板是否通过模板参数替换,并将添加到重载解决方案集。如果表达式格式正确,即如果所讨论的类型重载operator<<,则会发生这种情况。
中间的子表达式void()除了确保不选择某些用户定义的operator,(因为不能用void参数类型重载operator,)。
第三个也是最右边的子表达式std::string{}决定了detail::stringify函数的返回类型。
- 不要太离题,但我不理解你第一个函数中的decltype。为什么它有两个论点?这不是只会是std::string吗?
- @亚当:它没有两个参数;它只有一个参数,由带有逗号运算符的表达式组成。如果ss << t格式良好,则完整表达式的类型为std::string。如果不是,则替换失败,模板未实例化,因此选择第二个版本。
- 那么,为了应用sfinae,ss << t的表达必须出现在decltype中吗?体内的ss << t是否也有同样的作用?
- @adam——替换发生在函数类型(即其返回类型和参数类型)及其模板参数中使用的类型/表达式中。因此,虽然ss << t不一定需要出现在decltype中,但体内的ss << t并不能起到同样的作用,因为这样替换就成功了,并将导致一个难以编译的错误,而不是函数从重载集中脱离出来。
- 抓住了。因此,decltype提供了将表达式放入函数声明中的方法,而无需使用更复杂的方法,如enable_if。整洁的
- Woop woop。
- @Xeo在很久以前就已经否决了这一点,所以我不能在你的路上再派任何代表了:)这个答案是我第一次学会了这种过载解决的方法,所以谢谢你。
- @Praetorian-1已移除!这种错误真的很常见。现在,对于A+1,请为以前从未见过的可怜的吸盘解释问题中的decltype( blah, void(), std::string )!;)
- @雅克人,你是个很难对付的顾客:)补充了一个解释,看起来比答案的其余部分要长!
- @实际上,这就是为什么我不喜欢这种方法:纠正它,并评论到底发生了什么,把它扩大到一个点,它没有任何短于替代品!我们真的需要requires…
- 你也可以把我的答案和我的答案联系起来,在这里我也解释了这一切。P/CC @ YAKK
- @如果requires requires在概念lite中不是有效的语法,那么yakk"我们真的需要requires"会更有趣。在目前的措辞文件草案中有一个例子:template requires requires (T x) { x + x; } T add(T a, T b) { return a + b; }。
- @我看到这个问题了,如果我翻过前两个答案,我会在你的答案中找到我需要的东西。对不起,这个问题几乎重复了。
- @凯尔马耶斯:没问题,这就是重复投票的目的。介意我把这一个当作另一个的替罪羊来结束吗?
- @我想不是,谢谢。