使 `std::get` 与 SFINAE 一起玩得很好
Making `std::get` play nice with SFINAE
std::get
似乎对 SFINAE 不友好,如下面的测试用例所示:
template <class T, class C>
auto foo(C &c) -> decltype(std::get<T>(c)) {
return std::get<T>(c);
}
template <class>
void foo(...) { }
int main() {
std::tuple<int> tuple{42};
foo<int>(tuple); // Works fine
foo<double>(tuple); // Crashes and burns
}
目标是将对 foo
的第二次调用转移到第二次过载。实际上,libstdc++ 给出:
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/tuple:1290:14: fatal error: no matching function for call to '__get_helper2'
{ return std::__get_helper2<_Tp>(__t); }
^~~~~~~~~~~~~~~~~~~~~~~
libc++比较直接,直接static_assert
引爆:
/usr/include/c++/v1/tuple:801:5: fatal error: static_assert failed "type not found in type list"
static_assert ( value != -1, "type not found in type list" );
^ ~~~~~~~~~~~
我真的不想实现洋葱层检查 C
是否是 std::tuple
专业化,并在其参数中寻找 T
...
是否有理由 std::get
不对 SFINAE 友好?有没有比上面概述的更好的解决方法?
我找到了 ,但没有找到 std::get
。
来自 N4527(我认为它仍在标准中):
§ 20.4.2.6 (8):
Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.
根据标准,上面的程序是ill-formed。
讨论结束。
根据 [tuple.elem]:,std::get<T>
显然不是 SFINAE-friendly
template <class T, class... Types>
constexpr T& get(tuple<Types...>& t) noexcept;
// and the other like overloads
Requires: The type T
occurs exactly once in Types...
. Otherwise, the program is ill-formed.
std::get<I>
也明确不是 SFINAE-friendly。
至于其他问题:
Is there a reason for std::get
not to be SFINAE-friendly?
不知道。通常,这不是一个需要 SFINAE-ed 的点。所以我想这不被认为是需要完成的事情。与滚动浏览一堆 non-viable 候选选项相比,硬错误更容易理解。如果您认为 std::get<T>
是 SFINAE-friendly 有令人信服的理由,您可以提交关于它的 LWG 问题。
Is there a better workaround than what is outlined above?
当然可以。您可以编写自己的 SFINAE-friendly 版本 get
(请注意,它使用 C++17 fold expression):
template <class T, class... Types,
std::enable_if_t<(std::is_same<T, Types>::value + ...) == 1, int> = 0>
constexpr T& my_get(tuple<Types...>& t) noexcept {
return std::get<T>(t);
}
然后随心所欲地做。
不要在 std::get
上使用 SFINAE;这是不允许的。
这里有两个相对sfinae友好的方法来测试你是否可以using std::get; get<X>(t)
:
template<class T,std::size_t I>
using can_get=std::integral_constant<bool, I<std::tuple_size<T>::value>;
namespace helper{
template<class T, class Tuple>
struct can_get_type:std::false_type{};
template<class T, class...Ts>
struct can_get_type<T,std::tuple<Ts...>>:
std::integral_constant<bool, (std::is_same_v<T,Ts>+...)==1>
{};
}
template<class T,class Tuple>
using can_get=typename helpers::can_get_type<T,Tuple>::type;
那么您的代码如下:
template <class T, class C, std::enable_if_t<can_get_type<C,T>{},int> =0>
decltype(auto) foo(C &c) {
return std::get<T>(c);
}
STL 中几乎没有函数是 SFINAE 友好的,这是默认的。
也许这纯粹是历史原因。 (如“C++ 的所有默认值都是错误的”)。
但也许 post-facto 的理由是 SFINAE 的友好性是有代价的(例如编译时间)。
我没有证据,但我认为可以公平地说 SF 代码需要更长的编译时间,因为它在拒绝替代方案时必须“继续尝试”,而不是在出现第一个错误时就放弃。
正如@Barry 所说,它也有心理成本,因为 SFINAE 比硬错误更难推理。
(至少在“概念”清楚之前。)
如果用户想要 SFINAE,可以在特性的帮助下在非 SFINAE 友好的基础上构建(付出很多努力)。
例如,总是可以写(@Barry 写了 std::get
的等价物)
template<class In, class Out, class=std::enable_if_t<std::is_assignable<std::iterator_traits<Out>::reference, std::iterator_traits<In>::reference> >
Out friendly_copy(In first, In last, Out d_last){
return std::copy(first, last, d_first);
}
老实说,我发现自己以这种方式包装了很多 STL 函数,但要做到这一点需要做很多工作。
所以,我想应该有适合 SFINAE 的 STL 版本。
从某种意义上说,如果将 requires
添加到当前函数的签名中,这就会发生。
我不知道这是否正是计划,但这可能是将概念引入语言的副作用。
希望如此。
std::get
似乎对 SFINAE 不友好,如下面的测试用例所示:
template <class T, class C>
auto foo(C &c) -> decltype(std::get<T>(c)) {
return std::get<T>(c);
}
template <class>
void foo(...) { }
int main() {
std::tuple<int> tuple{42};
foo<int>(tuple); // Works fine
foo<double>(tuple); // Crashes and burns
}
目标是将对 foo
的第二次调用转移到第二次过载。实际上,libstdc++ 给出:
/usr/local/bin/../lib/gcc/x86_64-pc-linux-gnu/6.3.0/../../../../include/c++/6.3.0/tuple:1290:14: fatal error: no matching function for call to '__get_helper2'
{ return std::__get_helper2<_Tp>(__t); }
^~~~~~~~~~~~~~~~~~~~~~~
libc++比较直接,直接static_assert
引爆:
/usr/include/c++/v1/tuple:801:5: fatal error: static_assert failed "type not found in type list"
static_assert ( value != -1, "type not found in type list" );
^ ~~~~~~~~~~~
我真的不想实现洋葱层检查 C
是否是 std::tuple
专业化,并在其参数中寻找 T
...
是否有理由 std::get
不对 SFINAE 友好?有没有比上面概述的更好的解决方法?
我找到了 std::get
。
来自 N4527(我认为它仍在标准中):
§ 20.4.2.6 (8):
Requires: The type T occurs exactly once in Types.... Otherwise, the program is ill-formed.
根据标准,上面的程序是ill-formed。
讨论结束。
std::get<T>
显然不是 SFINAE-friendly
template <class T, class... Types> constexpr T& get(tuple<Types...>& t) noexcept; // and the other like overloads
Requires: The type
T
occurs exactly once inTypes...
. Otherwise, the program is ill-formed.
std::get<I>
也明确不是 SFINAE-friendly。
至于其他问题:
Is there a reason for
std::get
not to be SFINAE-friendly?
不知道。通常,这不是一个需要 SFINAE-ed 的点。所以我想这不被认为是需要完成的事情。与滚动浏览一堆 non-viable 候选选项相比,硬错误更容易理解。如果您认为 std::get<T>
是 SFINAE-friendly 有令人信服的理由,您可以提交关于它的 LWG 问题。
Is there a better workaround than what is outlined above?
当然可以。您可以编写自己的 SFINAE-friendly 版本 get
(请注意,它使用 C++17 fold expression):
template <class T, class... Types,
std::enable_if_t<(std::is_same<T, Types>::value + ...) == 1, int> = 0>
constexpr T& my_get(tuple<Types...>& t) noexcept {
return std::get<T>(t);
}
然后随心所欲地做。
不要在 std::get
上使用 SFINAE;这是不允许的。
这里有两个相对sfinae友好的方法来测试你是否可以using std::get; get<X>(t)
:
template<class T,std::size_t I>
using can_get=std::integral_constant<bool, I<std::tuple_size<T>::value>;
namespace helper{
template<class T, class Tuple>
struct can_get_type:std::false_type{};
template<class T, class...Ts>
struct can_get_type<T,std::tuple<Ts...>>:
std::integral_constant<bool, (std::is_same_v<T,Ts>+...)==1>
{};
}
template<class T,class Tuple>
using can_get=typename helpers::can_get_type<T,Tuple>::type;
那么您的代码如下:
template <class T, class C, std::enable_if_t<can_get_type<C,T>{},int> =0>
decltype(auto) foo(C &c) {
return std::get<T>(c);
}
STL 中几乎没有函数是 SFINAE 友好的,这是默认的。
也许这纯粹是历史原因。 (如“C++ 的所有默认值都是错误的”)。
但也许 post-facto 的理由是 SFINAE 的友好性是有代价的(例如编译时间)。 我没有证据,但我认为可以公平地说 SF 代码需要更长的编译时间,因为它在拒绝替代方案时必须“继续尝试”,而不是在出现第一个错误时就放弃。 正如@Barry 所说,它也有心理成本,因为 SFINAE 比硬错误更难推理。 (至少在“概念”清楚之前。)
如果用户想要 SFINAE,可以在特性的帮助下在非 SFINAE 友好的基础上构建(付出很多努力)。
例如,总是可以写(@Barry 写了 std::get
的等价物)
template<class In, class Out, class=std::enable_if_t<std::is_assignable<std::iterator_traits<Out>::reference, std::iterator_traits<In>::reference> >
Out friendly_copy(In first, In last, Out d_last){
return std::copy(first, last, d_first);
}
老实说,我发现自己以这种方式包装了很多 STL 函数,但要做到这一点需要做很多工作。
所以,我想应该有适合 SFINAE 的 STL 版本。
从某种意义上说,如果将 requires
添加到当前函数的签名中,这就会发生。
我不知道这是否正是计划,但这可能是将概念引入语言的副作用。
希望如此。