【问题标题】:SFINAE not always works in C++?SFINAE 并不总是在 C++ 中工作?
【发布时间】:2021-06-25 15:59:43
【问题描述】:

我正在使用 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&lt;X&gt;() 案例,第一个测试编译):

<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 机制?实现上述代码的正确方法应该是什么?

【问题讨论】:

  • 如果你不引用/调用失败的对象,SFINAE 就可以工作。你叫它。
  • @S.M.但我正在使用decltype() 的调用结果,仅派生结果类型,因此实际调用从未完成。
  • 没有办法将位于函数或函数模板的 body 中的东西问“这东西编译了吗?”。粗略地说,SFINAE 的工作方式是尝试实例化一堆模板(重载或特化),并拒绝不产生可编译 header(不是正文)的实例化。您不能尝试使用本身无效的参数来实例化模板。
  • 有骗子herehere。长话短说,不,您不能 SFINAE 或概念化 语句,包括那些在 lambda 中的语句。
  • 只有函数类型及其模板参数类型的immediate context中的无效类型和表达式才会导致推演失败。

标签: c++ c++17 sfinae


【解决方案1】:

我认为问题在于您在 lambda 主体中尝试基于 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
}

类型Get&lt;T&gt; 无法创建,理论上编译器可以从中恢复,但这可能代价高昂(图像错误发生在深层层次结构中)。 F2 正确失败,因为编译器只需要查看函数头是否正确。

如果您可以将所需的检查移至模板标题,那么理论上它可以工作。比如:


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-27
    • 1970-01-01
    相关资源
    最近更新 更多