如何检查下标运算符是否存在?
How to check for the existence of a subscript operator?
我想编写一个类型特征,它使用 SFINAE 检查类型是否存在下标表达式。我在下面的初步尝试似乎在下标表达式可能时有效,但在括号运算符不存在时无效。
#include <iostream>
#include <vector>
#include <cassert>
template<class T, class Index>
struct has_subscript_operator_impl
{
template<class T1,
class Reference = decltype(
(*std::declval<T*>())[std::declval<Index>()]
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
template<class>
static std::false_type test(...);
using type = decltype(test<T>(0));
};
template<class T, class Index>
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
struct doesnt_have_it {};
struct returns_void
{
void operator[](int) {}
};
struct returns_int
{
int operator[](int) { return 0; }
};
int main()
{
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
assert((!has_subscript_operator<doesnt_have_it,int>::value));
std::cout << "has_subscript_operator<returns_void,int>: " << has_subscript_operator<returns_void,int>::value << std::endl;
assert((!has_subscript_operator<returns_void,int>::value));
std::cout << "has_subscript_operator<returns_int,int>: " << has_subscript_operator<returns_int,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
std::cout << "has_subscript_operator<int*,int>: " << has_subscript_operator<int*,int>::value << std::endl;
assert((has_subscript_operator<int*,int>::value));
std::cout << "has_subscript_operator<std::vector<int>,int>: " << has_subscript_operator<std::vector<int>,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
return 0;
}
clang-3.4
的输出:
$ clang -std=c++11 -I. -lstdc++ test_has_subscript_operator.cpp
test_has_subscript_operator.cpp:10:14: error: type 'doesnt_have_it' does not provide a subscript operator
(*std::declval<T*>())[std::declval<Index>()]
^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
test_has_subscript_operator.cpp:25:1: note: in instantiation of template class 'has_subscript_operator_impl<doesnt_have_it, int>' requested here
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
^
test_has_subscript_operator.cpp:41:66: note: in instantiation of template type alias 'has_subscript_operator' requested here
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
^
1 error generated.
如何修复我的 has_subscript_operator
使其适用于所有类型?
SFINAE 仅在直接上下文中发生替换失败时才起作用。在实例化成员函数模板 test
时,模板参数 Index
已为人所知,因此您得到的不是替换失败,而是硬错误。
解决这个问题的技巧是通过向 test
添加一个额外的模板类型参数并将其默认为 Index
.
来再次推导 Index
template<class T1,
class IndexDeduced = Index, // <--- here
class Reference = decltype(
(*std::declval<T*>())[std::declval<IndexDeduced>()] // and use that here
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
现在您的代码可以正常工作了。
一旦你有了 C++11,编写类型特征就容易多了……你不需要使用省略号重载技巧。您可以在神奇的帮助下直接使用 decltype
表达式:
template <typename... >
using void_t = void;
我们有基本案例:
template<class T, class Index, typename = void>
struct has_subscript_operator : std::false_type { };
而我们的表达 SFINAE 有效案例:
template<class T, class Index>
struct has_subscript_operator<T, Index, void_t<
decltype(std::declval<T>()[std::declval<Index>()])
>> : std::true_type { };
然后你可以写同样的别名:
template <class T, class Index>
using has_subscript_operator_t = typename has_subscript_operator<T, Index>::type;
你也可以使用@Yakk 最喜欢的方法,给出他在每个答案中复制的样板文件:
namespace details {
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
然后你可以简单地写属性:
template <class T, class Index>
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);
template <class T, class Index>
using has_subscript = can_apply<subscript_t, T, Index>;
使用 C++20 Concepts:
template<class T, class I = std::size_t>
concept indexible = requires (T& t, const I& i) {
{t[i]};
};
用法:
static_assert(not indexible<std::list<int>>);
static_assert( indexible<std::string>);
static_assert( indexible<std::vector<bool>>);
static_assert(not indexible<std::map<std::string, int>>);
static_assert( indexible<std::map<std::string, int>, std::string>);
static_assert(not indexible<const std::map<std::string, int>, std::string>);
struct any {
template<class T>
operator const T&() const;
};
static_assert( indexible<std::map<std::string, int>, any>);
static_assert( indexible<std::map<char*, int>, any>);
struct Foo {
int operator[](int i);
int operator[](double i);
};
static_assert(not indexible<Foo, any>);
现场演示:https://godbolt.org/z/7GWK4PvM8
请注意,如果 operator[]
被不同的参数类型重载,使用 any
将失败。
我想编写一个类型特征,它使用 SFINAE 检查类型是否存在下标表达式。我在下面的初步尝试似乎在下标表达式可能时有效,但在括号运算符不存在时无效。
#include <iostream>
#include <vector>
#include <cassert>
template<class T, class Index>
struct has_subscript_operator_impl
{
template<class T1,
class Reference = decltype(
(*std::declval<T*>())[std::declval<Index>()]
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
template<class>
static std::false_type test(...);
using type = decltype(test<T>(0));
};
template<class T, class Index>
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
struct doesnt_have_it {};
struct returns_void
{
void operator[](int) {}
};
struct returns_int
{
int operator[](int) { return 0; }
};
int main()
{
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
assert((!has_subscript_operator<doesnt_have_it,int>::value));
std::cout << "has_subscript_operator<returns_void,int>: " << has_subscript_operator<returns_void,int>::value << std::endl;
assert((!has_subscript_operator<returns_void,int>::value));
std::cout << "has_subscript_operator<returns_int,int>: " << has_subscript_operator<returns_int,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
std::cout << "has_subscript_operator<int*,int>: " << has_subscript_operator<int*,int>::value << std::endl;
assert((has_subscript_operator<int*,int>::value));
std::cout << "has_subscript_operator<std::vector<int>,int>: " << has_subscript_operator<std::vector<int>,int>::value << std::endl;
assert((has_subscript_operator<returns_int,int>::value));
return 0;
}
clang-3.4
的输出:
$ clang -std=c++11 -I. -lstdc++ test_has_subscript_operator.cpp
test_has_subscript_operator.cpp:10:14: error: type 'doesnt_have_it' does not provide a subscript operator
(*std::declval<T*>())[std::declval<Index>()]
^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~
test_has_subscript_operator.cpp:25:1: note: in instantiation of template class 'has_subscript_operator_impl<doesnt_have_it, int>' requested here
using has_subscript_operator = typename has_subscript_operator_impl<T,Index>::type;
^
test_has_subscript_operator.cpp:41:66: note: in instantiation of template type alias 'has_subscript_operator' requested here
std::cout << "has_subscript_operator<doesnt_have_it,int>: " << has_subscript_operator<doesnt_have_it,int>::value << std::endl;
^
1 error generated.
如何修复我的 has_subscript_operator
使其适用于所有类型?
SFINAE 仅在直接上下文中发生替换失败时才起作用。在实例化成员函数模板 test
时,模板参数 Index
已为人所知,因此您得到的不是替换失败,而是硬错误。
解决这个问题的技巧是通过向 test
添加一个额外的模板类型参数并将其默认为 Index
.
Index
template<class T1,
class IndexDeduced = Index, // <--- here
class Reference = decltype(
(*std::declval<T*>())[std::declval<IndexDeduced>()] // and use that here
),
class = typename std::enable_if<
!std::is_void<Reference>::value
>::type>
static std::true_type test(int);
现在您的代码可以正常工作了。
一旦你有了 C++11,编写类型特征就容易多了……你不需要使用省略号重载技巧。您可以在神奇的帮助下直接使用 decltype
表达式:
template <typename... >
using void_t = void;
我们有基本案例:
template<class T, class Index, typename = void>
struct has_subscript_operator : std::false_type { };
而我们的表达 SFINAE 有效案例:
template<class T, class Index>
struct has_subscript_operator<T, Index, void_t<
decltype(std::declval<T>()[std::declval<Index>()])
>> : std::true_type { };
然后你可以写同样的别名:
template <class T, class Index>
using has_subscript_operator_t = typename has_subscript_operator<T, Index>::type;
你也可以使用@Yakk 最喜欢的方法,给出他在每个答案中复制的样板文件:
namespace details {
template<class...>struct voider{using type=void;};
template<class...Ts>using void_t=typename voider<Ts...>::type;
template<template<class...>class Z, class, class...Ts>
struct can_apply:
std::false_type
{};
template<template<class...>class Z, class...Ts>
struct can_apply<Z, void_t<Z<Ts...>>, Ts...>:
std::true_type
{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z,void,Ts...>;
然后你可以简单地写属性:
template <class T, class Index>
using subscript_t = decltype(std::declval<T>()[std::declval<Index>()]);
template <class T, class Index>
using has_subscript = can_apply<subscript_t, T, Index>;
使用 C++20 Concepts:
template<class T, class I = std::size_t>
concept indexible = requires (T& t, const I& i) {
{t[i]};
};
用法:
static_assert(not indexible<std::list<int>>);
static_assert( indexible<std::string>);
static_assert( indexible<std::vector<bool>>);
static_assert(not indexible<std::map<std::string, int>>);
static_assert( indexible<std::map<std::string, int>, std::string>);
static_assert(not indexible<const std::map<std::string, int>, std::string>);
struct any {
template<class T>
operator const T&() const;
};
static_assert( indexible<std::map<std::string, int>, any>);
static_assert( indexible<std::map<char*, int>, any>);
struct Foo {
int operator[](int i);
int operator[](double i);
};
static_assert(not indexible<Foo, any>);
现场演示:https://godbolt.org/z/7GWK4PvM8
请注意,如果 operator[]
被不同的参数类型重载,使用 any
将失败。