SFINAE 并不总是适用于 C++?
SFINAE not always works in C++?
我正在使用 C++17。我编写了以下代码,应该使用 SFINAE 来测试 lambda 是否可编译(lambda 在语法上总是正确的,但可能无法编译,例如,由于主体中没有使用某些方法):
#include <type_traits>
#include <sstream>
#include <iostream>
#include <tuple>
template <typename ... Ts>
struct Types {
using types = std::tuple<Ts...>;
template <size_t I>
using type = std::tuple_element_t<I, types>;
};
template <typename F, typename Enable = void, typename ... Ts>
struct Compilable : std::false_type {};
template <typename F, typename ... Ts>
struct Compilable<F,
std::void_t<decltype((*(F*)0)(Types<Ts...>{}))>, Ts...>
: std::true_type {};
template <typename ... Ts, typename F>
bool constexpr Comp(F const & f) {
return Compilable<F, void, Ts...>::value;
}
template <typename T>
void Test() {
std::cout << Comp<T>([](auto Ts){
typename decltype(Ts)::template type<0> * p = 0;
std::stringstream ss; ss << (*p); }) << std::endl;
}
struct X {};
int main() {
Test<int>();
Test<X>();
}
我希望由于 SFINAE 而无法编译的特化会被默默地排除,但它却抛出编译错误(对于第二种 Test<X>()
情况,第一次测试编译):
<source>:30:34: error: invalid operands to binary expression
('std::stringstream' (aka 'basic_stringstream<char>') and
'typename decltype(Ts)::type<0>' (aka 'X'))
std::stringstream ss; ss << (*p); }) << std::endl;
我做错了什么?我错误地使用了 SFINAE 机制吗?上面代码的正确实现方式应该是什么?
我认为问题是您在 lambda 的 body 中尝试基于 SFINAE 的错误。
如果我明白这对于从错误中恢复过来已经太晚了。
SFINAE 的基本思想是从重载集中删除不兼容的函数,而不是从编译失败中恢复,线条有点模糊,
但可能最好的经验法则是错误只需要发生在函数声明中。
例如
template<typename T>
struct Get
{
using type = typename T::type;
};
void F1(...){}
template<typename T, typename TT = typename Get<T>::type>
void F1(T i){}
void F2(...){}
template<typename T, typename TT = typename T::type>
void F2(T i){}
int main() {
//F1(3); //hard error
F2(3); //SFINAE - work fine
}
Type Get<T>
无法创建,理论上编译器可以从中恢复,但这可能代价高昂(图像错误发生在深层层次结构中)。
F2
正确失败,因为编译器只需要查看函数 header 看它是否正确。
如果您可以将所需的检查移动到模板 header 那么理论上它可以工作。类似于:
template <typename T>
void Test() {
std::cout << Comp<T>(
[](auto Ts, decltype((*(std::stringstream*)0) << (*(typename decltype(Ts)::template type<0>*)0))* = {})
{
typename decltype(Ts)::template type<0> * p = 0;
std::stringstream ss; ss << (*p);
}
) << std::endl;
}
第二个 lambda 参数在调用点进行评估,并且可能“明显”无法应用参数。
它不会按照您的意愿给出结果,但可以编译,可能您需要更新婴儿车类型以正确反映 lambda 中的操作。
我正在使用 C++17。我编写了以下代码,应该使用 SFINAE 来测试 lambda 是否可编译(lambda 在语法上总是正确的,但可能无法编译,例如,由于主体中没有使用某些方法):
#include <type_traits>
#include <sstream>
#include <iostream>
#include <tuple>
template <typename ... Ts>
struct Types {
using types = std::tuple<Ts...>;
template <size_t I>
using type = std::tuple_element_t<I, types>;
};
template <typename F, typename Enable = void, typename ... Ts>
struct Compilable : std::false_type {};
template <typename F, typename ... Ts>
struct Compilable<F,
std::void_t<decltype((*(F*)0)(Types<Ts...>{}))>, Ts...>
: std::true_type {};
template <typename ... Ts, typename F>
bool constexpr Comp(F const & f) {
return Compilable<F, void, Ts...>::value;
}
template <typename T>
void Test() {
std::cout << Comp<T>([](auto Ts){
typename decltype(Ts)::template type<0> * p = 0;
std::stringstream ss; ss << (*p); }) << std::endl;
}
struct X {};
int main() {
Test<int>();
Test<X>();
}
我希望由于 SFINAE 而无法编译的特化会被默默地排除,但它却抛出编译错误(对于第二种 Test<X>()
情况,第一次测试编译):
<source>:30:34: error: invalid operands to binary expression
('std::stringstream' (aka 'basic_stringstream<char>') and
'typename decltype(Ts)::type<0>' (aka 'X'))
std::stringstream ss; ss << (*p); }) << std::endl;
我做错了什么?我错误地使用了 SFINAE 机制吗?上面代码的正确实现方式应该是什么?
我认为问题是您在 lambda 的 body 中尝试基于 SFINAE 的错误。 如果我明白这对于从错误中恢复过来已经太晚了。
SFINAE 的基本思想是从重载集中删除不兼容的函数,而不是从编译失败中恢复,线条有点模糊, 但可能最好的经验法则是错误只需要发生在函数声明中。
例如
template<typename T>
struct Get
{
using type = typename T::type;
};
void F1(...){}
template<typename T, typename TT = typename Get<T>::type>
void F1(T i){}
void F2(...){}
template<typename T, typename TT = typename T::type>
void F2(T i){}
int main() {
//F1(3); //hard error
F2(3); //SFINAE - work fine
}
Type Get<T>
无法创建,理论上编译器可以从中恢复,但这可能代价高昂(图像错误发生在深层层次结构中)。
F2
正确失败,因为编译器只需要查看函数 header 看它是否正确。
如果您可以将所需的检查移动到模板 header 那么理论上它可以工作。类似于:
template <typename T>
void Test() {
std::cout << Comp<T>(
[](auto Ts, decltype((*(std::stringstream*)0) << (*(typename decltype(Ts)::template type<0>*)0))* = {})
{
typename decltype(Ts)::template type<0> * p = 0;
std::stringstream ss; ss << (*p);
}
) << std::endl;
}
第二个 lambda 参数在调用点进行评估,并且可能“明显”无法应用参数。 它不会按照您的意愿给出结果,但可以编译,可能您需要更新婴儿车类型以正确反映 lambda 中的操作。