SFINAE 并不总是适用于 C++?

SFINAE not always works in C++?

我正在使用 C++17。我编写了以下代码,应该使用 SFINAE 来测试 lambda 是否可编译(lambda 在语法上总是正确的,但可能无法编译,例如,由于主体中没有使用某些方法):

Try it online!

#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 中的操作。