不在直接上下文中的替换失败不会在 C++ 中引发硬错误

问题描述 投票:0回答:0

我是 C++ 模板的新手,刚刚学习 SFINAE 和 C++ 中的相关功能。

背景和代码

我正在阅读here的帖子。在那篇文章中,作者尝试实现 boost hana 的

is_valid
,用于测试对象是否满足某些属性。

这里要实现的功能非常简单:如果我可以在

.serialze()
上调用
obj
,那么就调用它。否则,回退使用
to_string()
.

我从那个帖子模仿的最终代码是:

template <typename UnnamedType> struct Container
{
// Let's put the test in private.
private:
    // We use std::declval to 'recreate' an object of 'UnnamedType'.
    // We use std::declval to also 'recreate' an object of type 'Param'.
    // We can use both of these recreated objects to test the validity!
    template <typename Param> constexpr auto testValidity(int /* unused */)
    -> decltype(std::declval<UnnamedType>()(std::declval<Param>()), std::true_type{})
    {
        // If substitution didn't fail, we can return a true_type.
        return std::true_type{};
    }

    template <typename Param> constexpr std::false_type testValidity(...)
    {
        // Our sink-hole returns a false_type.
        return std::false_type{};
    }

public:
    // A public operator() that accept the argument we wish to test onto the UnnamedType.
    // Notice that the return type is automatic!
    template <typename Param> constexpr auto operator()(Param&&)
    {
        // The argument is forwarded to one of the two overloads.
        // The SFINAE on the 'true_type' will come into play to dispatch.
        // Once again, we use the int for the precedence.
        return testValidity<Param>(int());
    }
};

auto l5 = [](auto&& t) -> decltype(std::forward<decltype(t)>(t).serialize()) { };
auto hasSerialize = Container<decltype(l5)>{};

template <class T> auto serialize(T&& obj) 
-> typename std::enable_if<decltype(hasSerialize(std::forward<T>(obj)))::value, std::string>::type
{
    return std::forward<T>(obj).serialize();
}

template <class T> auto serialize(T&& obj) 
-> typename std::enable_if<!decltype(hasSerialize(std::forward<T>(obj)))::value, std::string>::type
{
    return to_string(obj);
}

有点复杂,因为我尝试用完美转发来处理右值参数。

然后我需要一个类来测试:

class C {
 public:
  std::string serialize() && { return "This is C"; }  //NOTE: there is a ref-qualifier
};

std::string to_string(const C&) { return "This is C in to_string()"; }

int main() {
    C c{};
    std::cout << serialize(c) << std::endl;
}

环境与成果

当我在 MacOS (M1) 上用 clang 13.0.0 编译时:

clang++ -std=c++14 -O0
没有编译错误,输出是:“This is C in to_string()”。

问题

那我的问题来了: 在解析

serialze(c)
中的
main
调用时,编译器需要对
serialize()
函数进行subsitite。然后需要为
hasSerialize::operator()
实例化,因为它出现在
std::enable_if
中(在两个
serialize()
中)。

为了实例化

hasSerialize::operator()
,编译器需要为
testValidity<Param>
实例化,其中
Param
C&
,因为它出现在函数体中。

然后编译器会尝试替代

template <typename Param> constexpr auto testValidity(int /* unused */)
    -> decltype(std::declval<UnnamedType>()(std::declval<Param>()), std::true_type{})`

最后,编译器将尝试替换通用 lambda

[](auto&& t) -> decltype(std::forward<decltype(t)>(t).serialize()) { };

因为它出现在

decltype
的返回类型
testValidity
中。

替换失败发生在通用lambda处,因为

t
的类型是
C&
,但
.serialize()
只对
C&&
有效。这里的替换失败不在 直接上下文(如果我正确理解这里的答案)。

为什么编译器不抛出硬错误?

c++ templates lambda c++14 sfinae
© www.soinside.com 2019 - 2024. All rights reserved.