创建 "is_iterable" 特征时出现 SFINAE 问题 - 这是 gcc 错误吗?
SFINAE issue in creating an "is_iterable" trait - is this a gcc bug?
下面的代码尝试(不使用 c++11)创建一个特征来识别一个类型是否以 STL 方式迭代:
#include <iostream>
#include <vector>
template<typename C>
struct IsIterable
{
typedef char true_type;
typedef long false_type;
template<class T> static true_type is_beg_iterable(
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>()) == sizeof(true_type) };
};
int main() {
std::cout << IsIterable<std::vector<int>>::value << std::endl;
}
还有一个is_end_iterable
方法,这里省略
代码 fails 与 gcc 4.9.2 *(以及旧版本)and clang 并在 VS2012.我的断言是可变参数版本在重载决策中总是排在最后(因此应该没有歧义),所以 谁在这儿?
是否有跨平台的解决方法/替代方案?
我现在看到较新版本的 VS 也拒绝代码,所以最后一个问题变得更重要
以下作品(至少使用 gcc 4.9.2):
template<typename C>
struct IsIterable
{
typedef char true_type;
typedef long false_type;
template<class T> static true_type is_beg_iterable(int,
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>(0)) == sizeof(true_type) };
};
这仅在函数的实际参数比省略号转换更匹配时才有效。请记住,没有参数的具有默认值的参数不参与重载决策。
解决方法是添加另一个参数并传递一个参数:
template<class T> static true_type is_beg_iterable(int, // <- for disambiguation
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>(0)) == sizeof(true_type) };
// ^
从 C++17 开始,定义 is_iterable
特征的惯用方法是:
#include <type_traits>
#include <iterator>
namespace is_iterable_impl
{
using std::begin, std::end;
template<class T>
using check_specs = std::void_t<
std::enable_if_t<std::is_same_v<
decltype(begin(std::declval<T&>())), // has begin()
decltype(end(std::declval<T&>())) // has end()
>>, // ... begin() and end() are the same type ...
decltype(*begin(std::declval<T&>())) // ... which can be dereferenced
>;
template<class T, class = void>
struct is_iterable
: std::false_type
{};
template<class T>
struct is_iterable<T, check_specs<T>>
: std::true_type
{};
}
template<class T>
using is_iterable = is_iterable_impl::is_iterable<T>;
template<class T>
constexpr bool is_iterable_v = is_iterable<T>::value;
我们是 std::declval<T&>()
,所以我们的特征适用于数组。如您所见,std::begin
和 std::end
对数组有一个重载,它需要一个引用。
下面的代码尝试(不使用 c++11)创建一个特征来识别一个类型是否以 STL 方式迭代:
#include <iostream>
#include <vector>
template<typename C>
struct IsIterable
{
typedef char true_type;
typedef long false_type;
template<class T> static true_type is_beg_iterable(
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>()) == sizeof(true_type) };
};
int main() {
std::cout << IsIterable<std::vector<int>>::value << std::endl;
}
还有一个is_end_iterable
方法,这里省略
代码 fails 与 gcc 4.9.2 *(以及旧版本)and clang 并在 VS2012.我的断言是可变参数版本在重载决策中总是排在最后(因此应该没有歧义),所以 谁在这儿?
是否有跨平台的解决方法/替代方案?
我现在看到较新版本的 VS 也拒绝代码,所以最后一个问题变得更重要
以下作品(至少使用 gcc 4.9.2):
template<typename C>
struct IsIterable
{
typedef char true_type;
typedef long false_type;
template<class T> static true_type is_beg_iterable(int,
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>(0)) == sizeof(true_type) };
};
这仅在函数的实际参数比省略号转换更匹配时才有效。请记住,没有参数的具有默认值的参数不参与重载决策。
解决方法是添加另一个参数并传递一个参数:
template<class T> static true_type is_beg_iterable(int, // <- for disambiguation
typename T::const_iterator = C().begin());
template<class T> static false_type is_beg_iterable(...);
enum { value = sizeof(is_beg_iterable<C>(0)) == sizeof(true_type) };
// ^
从 C++17 开始,定义 is_iterable
特征的惯用方法是:
#include <type_traits>
#include <iterator>
namespace is_iterable_impl
{
using std::begin, std::end;
template<class T>
using check_specs = std::void_t<
std::enable_if_t<std::is_same_v<
decltype(begin(std::declval<T&>())), // has begin()
decltype(end(std::declval<T&>())) // has end()
>>, // ... begin() and end() are the same type ...
decltype(*begin(std::declval<T&>())) // ... which can be dereferenced
>;
template<class T, class = void>
struct is_iterable
: std::false_type
{};
template<class T>
struct is_iterable<T, check_specs<T>>
: std::true_type
{};
}
template<class T>
using is_iterable = is_iterable_impl::is_iterable<T>;
template<class T>
constexpr bool is_iterable_v = is_iterable<T>::value;
我们是 std::declval<T&>()
,所以我们的特征适用于数组。如您所见,std::begin
和 std::end
对数组有一个重载,它需要一个引用。