具有 decltype 的 C++ SFINAE:替换失败变成错误?
C++ SFINAE with decltype: substitution failure becomes an error?
此代码有效:
// Code A
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename T>
struct S {
template <typename Iter, typename = typename enable_if<is_constructible<T, decltype(*(declval<Iter>()))>::value>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin()); // stdout: S(Iter)
S<int> s2(1); // stdout: S(int)
}
但是下面这段代码不起作用。在下面的代码中,我只想继承 std::enable_if
,因此如果 std::enable_if
的选定版本具有成员 typedef,则 class is_iter_of
将具有成员 typedef type
type
.
// Code B
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename Iter, typename Target>
struct is_iter_of : public enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value> {}
template <typename T>
struct S {
template <typename Iter, typename = typename is_iter_of<Iter, T>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin());
S<int> s2(1); // this is line 22, error
}
错误信息:
In instantiation of 'struct is_iter_of<int, int>':
12:30: required by substitution of 'template<class Iter, class> S<T>::S(Iter) [with Iter = int; <template-parameter-1-2> = <missing>]'
22:16: required from here
8:72: error: invalid type argument of unary '*' (have 'int')
错误信息令人困惑:当然我希望模板替换失败..所以可以选择正确的构造函数。为什么 SFINAE 没有在 Code B
工作?如果 invalid type argument of unary '*' (have 'int')
冒犯了编译器,编译器也应该对 Code A
发出相同的错误。
问题是表达式 *int
(*(declval<Iter>())
) 无效,因此您的模板失败。您需要另一个级别的模板,所以我建议使用 void_t 方法:
通过派生自true_type
或fals_type
的精简概念制作is_iter_of
在 class 的定义中使用 enable_if
来启用迭代器构造函数。
要理解的关键是,您的构造函数之前需要 typename is_iter_of<Iter, T>::type
的类型,只是 struct is_iter_of
中的 enable_if
导致整个内容格式错误。由于没有后备模板,您遇到了编译器错误。
template<class...>
using voider = void;
template <typename Iter, typename Target, typename = void>
struct is_iter_of : std::false_type{};
template <typename Iter, typename Target>
struct is_iter_of<Iter, Target, voider<decltype(*(declval<Iter>()))>> : std::is_constructible<Target, decltype(*(declval<Iter>()))> {};
template <typename T>
struct S {
template <typename Iter, typename std::enable_if<is_iter_of<Iter, T>::value, int>::type = 0>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
Demo (C++11)
发生了什么事
如果 *(declval<Iter>())
是一个格式不正确的表达式 (*int
),附加的 voider
使得模板特化不是首选,因此回退基本模板 (std::false_type
)被选中。
否则,它将派生自 std::is_constructible``. In other words, it can still derive from
std::false_typeif the expression is well-formed but it's not constructibe, and
true_type` 否则。
问题是您正在尝试从 std::enable_if
扩展,但是您放在 enable if 中的表达式可能无效。由于您使用的 class 继承自该形式,因此您实例化的 class 从无效表达式继承,因此出现错误。
为 enable_if
表达式命名的一个简单解决方案是使用别名而不是 class:
template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;
SFINAE 仍可使用别名按预期工作。
这是因为实例化别名是您尝试应用 SFINAE 的函数的一部分。通过继承,表达式是被实例化的 class 的一部分,而不是函数。这就是您遇到硬错误的原因。
事实是,SFINAE 在您的案例中有多种应用方式。让我们来看看SFINAE可以发生在哪里:
enable_if< // here -------v
is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
// ^--- here
确实会发生SFINAE,因为如果bool参数为false,enable_if::type
将不存在,导致SFINAE。
但如果仔细观察,另一种类型可能不存在:decltype(*(std::declval<Iter>()))
。如果 Iter
是 int
,询问星号运算符的类型是没有意义的。所以 SFINAE 如果也在那里应用。
如果您作为 Iter
发送的每个 class 都具有可用的 *
运算符,那么您的继承解决方案将会有效。由于 int
它不存在,因此您正在向 std::is_constructible
发送一个不存在的类型,从而使构成基础 class 的整个表达式无效。
加上别名,整个使用std::enable_if
的表达式以应用SFINAE为准。而基础 class 方法只会将 SFINAE 应用于 std::enable_if
.
的结果
此代码有效:
// Code A
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename T>
struct S {
template <typename Iter, typename = typename enable_if<is_constructible<T, decltype(*(declval<Iter>()))>::value>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin()); // stdout: S(Iter)
S<int> s2(1); // stdout: S(int)
}
但是下面这段代码不起作用。在下面的代码中,我只想继承 std::enable_if
,因此如果 std::enable_if
的选定版本具有成员 typedef,则 class is_iter_of
将具有成员 typedef type
type
.
// Code B
#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;
template <typename Iter, typename Target>
struct is_iter_of : public enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value> {}
template <typename T>
struct S {
template <typename Iter, typename = typename is_iter_of<Iter, T>::type>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
int main()
{
vector<int> v;
S<int> s1(v.begin());
S<int> s2(1); // this is line 22, error
}
错误信息:
In instantiation of 'struct is_iter_of<int, int>':
12:30: required by substitution of 'template<class Iter, class> S<T>::S(Iter) [with Iter = int; <template-parameter-1-2> = <missing>]'
22:16: required from here
8:72: error: invalid type argument of unary '*' (have 'int')
错误信息令人困惑:当然我希望模板替换失败..所以可以选择正确的构造函数。为什么 SFINAE 没有在 Code B
工作?如果 invalid type argument of unary '*' (have 'int')
冒犯了编译器,编译器也应该对 Code A
发出相同的错误。
问题是表达式 *int
(*(declval<Iter>())
) 无效,因此您的模板失败。您需要另一个级别的模板,所以我建议使用 void_t 方法:
通过派生自
true_type
或fals_type
的精简概念制作在 class 的定义中使用
enable_if
来启用迭代器构造函数。
is_iter_of
要理解的关键是,您的构造函数之前需要 typename is_iter_of<Iter, T>::type
的类型,只是 struct is_iter_of
中的 enable_if
导致整个内容格式错误。由于没有后备模板,您遇到了编译器错误。
template<class...>
using voider = void;
template <typename Iter, typename Target, typename = void>
struct is_iter_of : std::false_type{};
template <typename Iter, typename Target>
struct is_iter_of<Iter, Target, voider<decltype(*(declval<Iter>()))>> : std::is_constructible<Target, decltype(*(declval<Iter>()))> {};
template <typename T>
struct S {
template <typename Iter, typename std::enable_if<is_iter_of<Iter, T>::value, int>::type = 0>
S(Iter) { cout << "S(Iter)" << endl; }
S(int) { cout << "S(int)" << endl; }
};
Demo (C++11)
发生了什么事
如果 *(declval<Iter>())
是一个格式不正确的表达式 (*int
),附加的 voider
使得模板特化不是首选,因此回退基本模板 (std::false_type
)被选中。
否则,它将派生自 std::is_constructible``. In other words, it can still derive from
std::false_typeif the expression is well-formed but it's not constructibe, and
true_type` 否则。
问题是您正在尝试从 std::enable_if
扩展,但是您放在 enable if 中的表达式可能无效。由于您使用的 class 继承自该形式,因此您实例化的 class 从无效表达式继承,因此出现错误。
为 enable_if
表达式命名的一个简单解决方案是使用别名而不是 class:
template <typename Iter, typename Target>
using is_iter_of = enable_if<is_constructible<Target, decltype(*(declval<Iter>()))>::value>;
SFINAE 仍可使用别名按预期工作。
这是因为实例化别名是您尝试应用 SFINAE 的函数的一部分。通过继承,表达式是被实例化的 class 的一部分,而不是函数。这就是您遇到硬错误的原因。
事实是,SFINAE 在您的案例中有多种应用方式。让我们来看看SFINAE可以发生在哪里:
enable_if< // here -------v
is_constructible<Target, decltype(*(declval<Iter>()))>::value
>::type
// ^--- here
确实会发生SFINAE,因为如果bool参数为false,enable_if::type
将不存在,导致SFINAE。
但如果仔细观察,另一种类型可能不存在:decltype(*(std::declval<Iter>()))
。如果 Iter
是 int
,询问星号运算符的类型是没有意义的。所以 SFINAE 如果也在那里应用。
如果您作为 Iter
发送的每个 class 都具有可用的 *
运算符,那么您的继承解决方案将会有效。由于 int
它不存在,因此您正在向 std::is_constructible
发送一个不存在的类型,从而使构成基础 class 的整个表达式无效。
加上别名,整个使用std::enable_if
的表达式以应用SFINAE为准。而基础 class 方法只会将 SFINAE 应用于 std::enable_if
.