value wrapper and SFINAE failure
C++17中的value wrapper 如optional 和variant给我们带来很大便利。假定我们要实现一个自己的value wrapper,代码如下:
#include <iostream>
#include <fmt/format.h>
template<class T>
struct wrapper{
T t;
template<class F>
auto pass_to(F f)const->decltype(f(t))
{
fmt::print("pass_to const\n");
return f(t);
}
template<class F>
auto pass_to(F f)->decltype(f(t))
{
fmt::print("pass_to not const\n");
return f(t);
}
};
struct Foo{
void do_something(){};
};
int main()
{
wrapper<Foo> f;//[1]
f.pass_to([](auto &&x){x.do_something(); }); //[2]
}
这里wrapper是我们的value wrapper。[2]处的f.pass_to将回调lambda,lambda的参数为wrapper<Foo>的唯一成员变量t。然而上述代码有个bug,你能看出来吗?
bug是pass_to的sfinae将产生hard error。如gcc 的error output:
<source>: In instantiation of 'main()::<lambda(auto:19&&)> [with auto:19 = const Foo&]':
<source>:7:39: required by substitution of 'template<class F> decltype (f(((const wrapper<Foo>*)this)->wrapper<Foo>::t)) wrapper<Foo>::pass_to(F) const [with F = main()::<lambda(auto:19&&)>]'
<source>:25:14: required from here
<source>:25:42: error: passing 'const Foo' as 'this' argument discards qualifiers [-fpermissive]
25 | f.pass_to([](auto &&x){x.do_something(); });
| ~~~~~~~~~~~~~~^~
<source>:20:10: note: in call to 'void Foo::do_something()'
20 | void do_something(){};
| ^~~~~~~~~~~~
两个pass_to都有->decltype(f(t))的sfinae,因此当我们传递lambda时,需要先进行sfinae决议,然而在sfinae的时候需要知道f(t)的返回类型,对于没有尾置返回的lambda,编译器必须解析lambda的全部代码决定返回类型而不能只解析lambda的签名。当如下const pass_to 成员函数做sfinae时,t是常成员变量,x是t左值引用,也是常量,而do_something()函数并不是常成员函数,因此Sfinae 时x.do_something()产出hard error。
...
template<class F>
auto pass_to(F f)const->decltype(f(t))
{
fmt::print("pass_to const\n");
return f(t);
}
...
有啥办法解决对pass_to函数sfinae时产生hard eror的问题吗?在c++20以前是没有办法的,C++23引入deduce this的新语言特性可完美解决该问题。
例子代码选自cppcon上sy brand的How to write a value wrapper演讲。
Posted 2021-10-08